aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorgingerBill <gingerBill@users.noreply.github.com>2024-06-20 11:47:01 +0100
committerGitHub <noreply@github.com>2024-06-20 11:47:01 +0100
commit5dc98336a82463df6df9be1ff4bd2946f833b57b (patch)
treea5b639ce604414bdac89e1ea2ae582683eea46c9 /src
parent23351ca8bec5862be531e886a3a3513c31bb2c30 (diff)
parent1128bd1d7f81ef1f7865fb4e0975084ea8023e5b (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.cpp58
-rw-r--r--src/checker_builtin_procs.hpp4
-rw-r--r--src/llvm_backend_proc.cpp178
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);