Browse Source

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.
Feoramund 1 year ago
parent
commit
5b5402fb23

+ 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

+ 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

@@ -5089,15 +5089,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:
@@ -5107,6 +5101,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) {
@@ -5120,6 +5117,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);