From 5b5402fb23d0902810f18f805a72120f5243bddd Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 12 Jun 2024 13:07:13 -0400 Subject: Add `intrinsics.syscall_bsd` This is a BSD-style syscall that checks for a high Carry Flag as the error state. If the CF is high, the boolean return value is false, and if it is low (no errors) then the boolean return value is true. --- src/llvm_backend_proc.cpp | 178 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 134 insertions(+), 44 deletions(-) (limited to 'src/llvm_backend_proc.cpp') 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); -- cgit v1.2.3