diff options
| author | gingerBill <gingerBill@users.noreply.github.com> | 2024-06-20 11:47:01 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-06-20 11:47:01 +0100 |
| commit | 5dc98336a82463df6df9be1ff4bd2946f833b57b (patch) | |
| tree | a5b639ce604414bdac89e1ea2ae582683eea46c9 /src | |
| parent | 23351ca8bec5862be531e886a3a3513c31bb2c30 (diff) | |
| parent | 1128bd1d7f81ef1f7865fb4e0975084ea8023e5b (diff) | |
Merge pull request #3524 from Feoramund/freebsd-amd64-syscall-errno
Add `intrinsics.syscall_bsd`
Diffstat (limited to 'src')
| -rw-r--r-- | src/check_builtin.cpp | 58 | ||||
| -rw-r--r-- | src/checker_builtin_procs.hpp | 4 | ||||
| -rw-r--r-- | src/llvm_backend_proc.cpp | 178 |
3 files changed, 189 insertions, 51 deletions
diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 203ae11ea..47abd42cf 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -5115,15 +5115,9 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As isize max_arg_count = 32; switch (build_context.metrics.os) { - case TargetOs_windows: - case TargetOs_freestanding: - error(call, "'%.*s' is not supported on this platform (%.*s)", LIT(builtin_name), LIT(target_os_names[build_context.metrics.os])); - break; case TargetOs_darwin: case TargetOs_linux: case TargetOs_essence: - case TargetOs_freebsd: - case TargetOs_openbsd: case TargetOs_haiku: switch (build_context.metrics.arch) { case TargetArch_i386: @@ -5133,6 +5127,9 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As break; } break; + default: + error(call, "'%.*s' is not supported on this platform (%.*s)", LIT(builtin_name), LIT(target_os_names[build_context.metrics.os])); + break; } if (ce->args.count > max_arg_count) { @@ -5146,6 +5143,55 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return true; } break; + case BuiltinProc_syscall_bsd: + { + convert_to_typed(c, operand, t_uintptr); + if (!is_type_uintptr(operand->type)) { + gbString t = type_to_string(operand->type); + error(operand->expr, "Argument 0 must be of type 'uintptr', got %s", t); + gb_string_free(t); + } + for (isize i = 1; i < ce->args.count; i++) { + Operand x = {}; + check_expr(c, &x, ce->args[i]); + if (x.mode != Addressing_Invalid) { + convert_to_typed(c, &x, t_uintptr); + } + convert_to_typed(c, &x, t_uintptr); + if (!is_type_uintptr(x.type)) { + gbString t = type_to_string(x.type); + error(x.expr, "Argument %td must be of type 'uintptr', got %s", i, t); + gb_string_free(t); + } + } + + isize max_arg_count = 32; + + switch (build_context.metrics.os) { + case TargetOs_freebsd: + case TargetOs_netbsd: + case TargetOs_openbsd: + switch (build_context.metrics.arch) { + case TargetArch_amd64: + case TargetArch_arm64: + max_arg_count = 7; + break; + } + break; + default: + error(call, "'%.*s' is not supported on this platform (%.*s)", LIT(builtin_name), LIT(target_os_names[build_context.metrics.os])); + break; + } + + if (ce->args.count > max_arg_count) { + error(ast_end_token(call), "'%.*s' has a maximum of %td arguments on this platform (%.*s), got %td", LIT(builtin_name), max_arg_count, LIT(target_os_names[build_context.metrics.os]), ce->args.count); + } + + operand->mode = Addressing_Value; + operand->type = make_optional_ok_type(t_uintptr); + return true; + } + break; case BuiltinProc_type_base_type: diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 35acad42f..a2b8a5361 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -192,6 +192,7 @@ BuiltinProc__simd_end, // Platform specific intrinsics BuiltinProc_syscall, + BuiltinProc_syscall_bsd, BuiltinProc_x86_cpuid, BuiltinProc_x86_xgetbv, @@ -512,7 +513,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {STR_LIT("syscall"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("syscall"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("syscall_bsd"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("x86_cpuid"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("x86_xgetbv"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 87f75fb1d..958b4fd38 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2747,26 +2747,10 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu { GB_ASSERT(arg_count <= 7); - // FreeBSD additionally clobbers r8, r9, r10, but they - // can also be used to pass in arguments, so this needs - // to be handled in two parts. - bool clobber_arg_regs[7] = { - false, false, false, false, false, false, false - }; - if (build_context.metrics.os == TargetOs_freebsd) { - clobber_arg_regs[4] = true; // r10 - clobber_arg_regs[5] = true; // r8 - clobber_arg_regs[6] = true; // r9 - } - char asm_string[] = "syscall"; gbString constraints = gb_string_make(heap_allocator(), "={rax}"); for (unsigned i = 0; i < arg_count; i++) { - if (!clobber_arg_regs[i]) { - constraints = gb_string_appendc(constraints, ",{"); - } else { - constraints = gb_string_appendc(constraints, ",+{"); - } + constraints = gb_string_appendc(constraints, ",{"); static char const *regs[] = { "rax", "rdi", @@ -2790,36 +2774,9 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu // Some but not all system calls will additionally // clobber memory. // - // As a fix for CVE-2019-5595, FreeBSD started - // clobbering R8, R9, and R10, instead of restoring - // them. Additionally unlike Linux, instead of - // returning negative errno, positive errno is - // returned and CF is set. - // // TODO: // * Figure out what Darwin does. - // * Add some extra handling to propagate CF back - // up to the caller on FreeBSD systems so that - // the caller knows that the return value is - // positive errno. constraints = gb_string_appendc(constraints, ",~{rcx},~{r11},~{memory}"); - if (build_context.metrics.os == TargetOs_freebsd) { - // Second half of dealing with FreeBSD's system - // call semantics. Explicitly clobber the registers - // that were not used to pass in arguments, and - // then clobber RFLAGS. - if (arg_count < 5) { - constraints = gb_string_appendc(constraints, ",~{r10}"); - } - if (arg_count < 6) { - constraints = gb_string_appendc(constraints, ",~{r8}"); - } - if (arg_count < 7) { - constraints = gb_string_appendc(constraints, ",~{r9}"); - } - constraints = gb_string_appendc(constraints, ",~{cc}"); - } - inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints)); } break; @@ -2927,6 +2884,139 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu res.type = t_uintptr; return res; } + case BuiltinProc_syscall_bsd: + { + // This is a BSD-style syscall where errors are indicated by a high + // Carry Flag and a positive return value, allowing the kernel to + // return any value that fits into a machine word. + // + // This is unlike Linux, where errors are indicated by a negative + // return value, limiting what can be expressed in one result. + unsigned arg_count = cast(unsigned)ce->args.count; + LLVMValueRef *args = gb_alloc_array(permanent_allocator(), LLVMValueRef, arg_count); + for_array(i, ce->args) { + lbValue arg = lb_build_expr(p, ce->args[i]); + arg = lb_emit_conv(p, arg, t_uintptr); + args[i] = arg.value; + } + + LLVMTypeRef llvm_uintptr = lb_type(p->module, t_uintptr); + LLVMTypeRef *llvm_arg_types = gb_alloc_array(permanent_allocator(), LLVMTypeRef, arg_count); + for (unsigned i = 0; i < arg_count; i++) { + llvm_arg_types[i] = llvm_uintptr; + } + + LLVMTypeRef *results = gb_alloc_array(permanent_allocator(), LLVMTypeRef, 2); + results[0] = lb_type(p->module, t_uintptr); + results[1] = lb_type(p->module, t_bool); + LLVMTypeRef llvm_results = LLVMStructTypeInContext(p->module->ctx, results, 2, false); + + LLVMTypeRef func_type = LLVMFunctionType(llvm_results, llvm_arg_types, arg_count, false); + + LLVMValueRef inline_asm = nullptr; + + switch (build_context.metrics.arch) { + case TargetArch_amd64: + { + GB_ASSERT(arg_count <= 7); + + char asm_string[] = "syscall; setnb %cl"; + + // Using CL as an output; RCX doesn't need to get clobbered later. + gbString constraints = gb_string_make(heap_allocator(), "={rax},={cl}"); + for (unsigned i = 0; i < arg_count; i++) { + constraints = gb_string_appendc(constraints, ",{"); + static char const *regs[] = { + "rax", + "rdi", + "rsi", + "rdx", + "r10", + "r8", + "r9", + }; + constraints = gb_string_appendc(constraints, regs[i]); + constraints = gb_string_appendc(constraints, "}"); + } + + // NOTE(Feoramund): If you're experiencing instability + // regarding syscalls during optimized builds, it is + // possible that the ABI has changed for your platform, + // or I've missed a register clobber. + // + // Documentation on this topic is sparse, but I was able to + // determine what registers were being clobbered by adding + // dummy values to them, setting a breakpoint after the + // syscall, and checking the state of the registers afterwards. + // + // Be advised that manually stepping through a debugger may + // cause the kernel to not return via sysret, which will + // preserve register state that normally would've been + // otherwise clobbered. + // + // It is also possible that some syscalls clobber different registers. + + if (build_context.metrics.os == TargetOs_freebsd) { + // As a fix for CVE-2019-5595, FreeBSD started + // clobbering R8, R9, and R10, instead of restoring + // them. + // + // More info here: + // + // https://www.freebsd.org/security/advisories/FreeBSD-SA-19:01.syscall.asc + // https://github.com/freebsd/freebsd-src/blob/098dbd7ff7f3da9dda03802cdb2d8755f816eada/sys/amd64/amd64/exception.S#L605 + // https://stackoverflow.com/q/66878250 + constraints = gb_string_appendc(constraints, ",~{r8},~{r9},~{r10}"); + } + + // Both FreeBSD and NetBSD might clobber RDX. + // + // For NetBSD, it was clobbered during a call to sysctl. + // + // For FreeBSD, it's listed as "return value 2" in their + // AMD64 assembly, so there's no guarantee that it will persist. + constraints = gb_string_appendc(constraints, ",~{rdx},~{r11},~{cc},~{memory}"); + + inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints)); + } + break; + case TargetArch_arm64: + { + GB_ASSERT(arg_count <= 7); + + char asm_string[] = "svc #0; cset x8, cc"; + gbString constraints = gb_string_make(heap_allocator(), "={x0},={x8}"); + for (unsigned i = 0; i < arg_count; i++) { + constraints = gb_string_appendc(constraints, ",{"); + static char const *regs[] = { + "x8", + "x0", + "x1", + "x2", + "x3", + "x4", + "x5", + }; + constraints = gb_string_appendc(constraints, regs[i]); + constraints = gb_string_appendc(constraints, "}"); + } + + // FreeBSD clobbered x1 on a call to sysctl. + constraints = gb_string_appendc(constraints, ",~{x1},~{cc},~{memory}"); + + inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints)); + } + break; + default: + GB_PANIC("Unsupported platform"); + } + + lbValue res = {}; + res.value = LLVMBuildCall2(p->builder, func_type, inline_asm, args, arg_count, ""); + res.type = make_optional_ok_type(t_uintptr, true); + + return res; + } case BuiltinProc_objc_send: return lb_handle_objc_send(p, expr); |