浏览代码

Merge pull request #3524 from Feoramund/freebsd-amd64-syscall-errno

Add `intrinsics.syscall_bsd`
gingerBill 1 年之前
父节点
当前提交
5dc98336a8

+ 2 - 0
base/intrinsics/intrinsics.odin

@@ -73,6 +73,8 @@ expect :: proc(val, expected_val: T) -> T ---
 
 // Linux and Darwin Only
 syscall :: proc(id: uintptr, args: ..uintptr) -> uintptr ---
+// FreeBSD, NetBSD, et cetera
+syscall_bsd :: proc(id: uintptr, args: ..uintptr) -> (uintptr, bool) ---
 
 
 // Atomics

+ 7 - 7
core/sync/futex_netbsd.odin

@@ -30,8 +30,8 @@ get_last_error :: proc "contextless" () -> int {
 }
 
 _futex_wait :: proc "contextless" (futex: ^Futex, expected: u32) -> bool {
-	if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), 0, 0, 0) == -1 {
-		switch get_last_error() {
+	if error, ok := intrinsics.syscall_bsd(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), 0, 0, 0); !ok {
+		switch error {
 		case EINTR, EAGAIN:
 			return true
 		case:
@@ -45,11 +45,11 @@ _futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, du
 	if duration <= 0 {
 		return false
 	}
-	if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), cast(uintptr) &Time_Spec{
+	if error, ok := intrinsics.syscall_bsd(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), cast(uintptr) &Time_Spec{
 		time_sec  = cast(uint)(duration / 1e9),
 		time_nsec = cast(uint)(duration % 1e9),
-	}, 0, 0) == -1 {
-		switch get_last_error() {
+	}, 0, 0); !ok {
+		switch error {
 		case EINTR, EAGAIN:
 			return true
 		case ETIMEDOUT:
@@ -62,13 +62,13 @@ _futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, du
 }
 
 _futex_signal :: proc "contextless" (futex: ^Futex) {
-	if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, 1, 0, 0, 0) == -1 {
+	if _, ok := intrinsics.syscall_bsd(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, 1, 0, 0, 0); !ok {
 		_panic("futex_wake_single failure")
 	}
 }
 
 _futex_broadcast :: proc "contextless" (futex: ^Futex)  {
-	if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, uintptr(max(i32)), 0, 0, 0) == -1 {
+	if _, ok := intrinsics.syscall_bsd(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, uintptr(max(i32)), 0, 0, 0); !ok {
 		_panic("_futex_wake_all failure")
 	}
 }

+ 1 - 1
core/sys/info/platform_freebsd.odin

@@ -12,7 +12,7 @@ version_string_buf: [1024]u8
 init_os_version :: proc () {
 	os_version.platform = .FreeBSD
 
-	kernel_version_buf: [129]u8
+	kernel_version_buf: [1024]u8
 
 	b := strings.builder_from_bytes(version_string_buf[:])
 	// Retrieve kernel info using `sysctl`, e.g. FreeBSD 13.1-RELEASE-p2 GENERIC

+ 4 - 3
core/sys/unix/sysctl_freebsd.odin

@@ -5,14 +5,15 @@ import "base:intrinsics"
 
 sysctl :: proc(mib: []i32, val: ^$T) -> (ok: bool) {
 	mib := mib
-	result_size := i64(size_of(T))
+	result_size := u64(size_of(T))
 
-	res := intrinsics.syscall(SYS_sysctl,
+	res: uintptr
+	res, ok = intrinsics.syscall_bsd(SYS_sysctl,
 		uintptr(raw_data(mib)), uintptr(len(mib)),
 		uintptr(val), uintptr(&result_size),
 		uintptr(0), uintptr(0),
 	)
-	return res == 0
+	return
 }
 
 // See /usr/include/sys/sysctl.h for details

+ 52 - 6
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:

+ 3 - 1
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},
 

+ 134 - 44
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);