From afec321db2ba30581d04e369d1864140ccfd20a6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 17 Aug 2022 16:09:03 +0100 Subject: [PATCH] Add `core:sys/valgrind` package for valgrind, memcheck, and callgrind --- core/sys/valgrind/callgrind.odin | 63 +++++++++++ core/sys/valgrind/memcheck.odin | 169 ++++++++++++++++++++++++++++ core/sys/valgrind/valgrind.odin | 182 +++++++++++++++++++++++++++++++ 3 files changed, 414 insertions(+) create mode 100644 core/sys/valgrind/callgrind.odin create mode 100644 core/sys/valgrind/memcheck.odin create mode 100644 core/sys/valgrind/valgrind.odin diff --git a/core/sys/valgrind/callgrind.odin b/core/sys/valgrind/callgrind.odin new file mode 100644 index 000000000..1396f82ad --- /dev/null +++ b/core/sys/valgrind/callgrind.odin @@ -0,0 +1,63 @@ +//+build amd64 +package sys_valgrind + +import "core:intrinsics" + +Callgrind_Client_Request :: enum uintptr { + Dump_Stats = 'C'<<24 | 'T'<<16, + Zero_Stats, + Toggle_Collect, + Dump_Stats_At, + Start_Instrumentation, + Stop_Instrumentation, +} + +@(require_results) +callgrind_client_request_expr :: proc "c" (default: uintptr, request: Callgrind_Client_Request, a0, a1, a2, a3, a4: uintptr) -> uintptr { + return intrinsics.valgrind_client_request(default, uintptr(request), a0, a1, a2, a3, a4) +} +callgrind_client_request_stmt :: proc "c" (request: Callgrind_Client_Request, a0, a1, a2, a3, a4: uintptr) { + _ = intrinsics.valgrind_client_request(0, uintptr(request), a0, a1, a2, a3, a4) +} + +// Dump current state of cost centres, and zero them afterwards. +dump_stats :: proc "c" () { + callgrind_client_request_stmt(.Dump_Stats, 0, 0, 0, 0, 0) +} + +// Zero cost centres +zero_stats :: proc "c" () { + callgrind_client_request_stmt(.Zero_Stats, 0, 0, 0, 0, 0) +} + +// Toggles collection state. +// The collection state specifies whether the happening of events should be noted or +// if they are to be ignored. Events are noted by increment of counters in a cost centre. +toggle_collect :: proc "c" () { + callgrind_client_request_stmt(.Toggle_Collect, 0, 0, 0, 0, 0) +} + +// Dump current state of cost centres, and zero them afterwards. +// The argument is appended to a string stating the reason which triggered +// the dump. This string is written as a description field into the +// profile data dump. +dump_stats_at :: proc "c" (pos_str: rawptr) { + callgrind_client_request_stmt(.Dump_Stats_At, uintptr(pos_str), 0, 0, 0, 0) +} + +// Start full callgrind instrumentation if not already switched on. +// When cache simulation is done, it will flush the simulated cache; +// this will lead to an artificial cache warmup phase afterwards with +// cache misses which would not have happened in reality. +start_instrumentation :: proc "c" () { + callgrind_client_request_stmt(.Start_Instrumentation, 0, 0, 0, 0, 0) +} + +// Stop full callgrind instrumentation if not already switched off. +// This flushes Valgrinds translation cache, and does no additional instrumentation +// afterwards, which effectivly will run at the same speed as the "none" tool (ie. at minimal slowdown). +// Use this to bypass Callgrind aggregation for uninteresting code parts. +// To start Callgrind in this mode to ignore the setup phase, use the option "--instr-atstart=no". +stop_instrumentation :: proc "c" () { + callgrind_client_request_stmt(.Stop_Instrumentation, 0, 0, 0, 0, 0) +} \ No newline at end of file diff --git a/core/sys/valgrind/memcheck.odin b/core/sys/valgrind/memcheck.odin new file mode 100644 index 000000000..99c65272a --- /dev/null +++ b/core/sys/valgrind/memcheck.odin @@ -0,0 +1,169 @@ +//+build amd64 +package sys_valgrind + +import "core:intrinsics" + +Mem_Check_Client_Request :: enum uintptr { + Make_Mem_No_Access = 'M'<<24 | 'C'<<16, + Make_Mem_Undefined, + Make_Mem_Defined, + Discard, + Check_Mem_Is_Addressable, + Check_Mem_Is_Defined, + Do_Leak_Check, + Count_Leaks, + Get_Vbits, + Set_Vbits, + Create_Block, + Make_Mem_Defined_If_Addressable, + Count_Leak_Blocks, + Enable_Addr_Error_Reporting_In_Range, + Disable_Addr_Error_Reporting_In_Range, +} + +@(require_results) +mem_check_client_request_expr :: proc "c" (default: uintptr, request: Mem_Check_Client_Request, a0, a1, a2, a3, a4: uintptr) -> uintptr { + return intrinsics.valgrind_client_request(default, uintptr(request), a0, a1, a2, a3, a4) +} +mem_check_client_request_stmt :: proc "c" (request: Mem_Check_Client_Request, a0, a1, a2, a3, a4: uintptr) { + _ = intrinsics.valgrind_client_request(0, uintptr(request), a0, a1, a2, a3, a4) +} + +// Mark memory at `raw_data(qzz)` as unaddressable for `len(qzz)` bytes. +// Returns true when run on Valgrind and false otherwise. +make_mem_no_access :: proc "c" (qzz: []byte) -> bool { + return 0 != mem_check_client_request_expr(0, .Make_Mem_No_Access, uintptr(raw_data(qzz)), uintptr(len(qzz)), 0, 0, 0) +} +// Mark memory at `raw_data(qzz)` as addressable but undefined for `len(qzz)` bytes. +// Returns true when run on Valgrind and false otherwise. +make_mem_undefined :: proc "c" (qzz: []byte) -> bool { + return 0 != mem_check_client_request_expr(0, .Make_Mem_Undefined, uintptr(raw_data(qzz)), uintptr(len(qzz)), 0, 0, 0) +} +// Mark memory at `raw_data(qzz)` as addressable for `len(qzz)` bytes. +// Returns true when run on Valgrind and false otherwise. +make_mem_defined :: proc "c" (qzz: []byte) -> bool { + return 0 != mem_check_client_request_expr(0, .Make_Mem_Defined, uintptr(raw_data(qzz)), uintptr(len(qzz)), 0, 0, 0) +} + +// Check that memory at `raw_data(qzz)` is addressable for `len(qzz)` bytes. +// If suitable addressibility is not established, Valgrind prints an error +// message and returns the address of the first offending byte. +// Otherwise it returns zero. +check_mem_is_addressable :: proc "c" (qzz: []byte) -> uintptr { + return mem_check_client_request_expr(0, .Check_Mem_Is_Addressable, uintptr(raw_data(qzz)), uintptr(len(qzz)), 0, 0, 0) +} +// Check that memory at `raw_data(qzz)` is addressable and defined for `len(qzz)` bytes. +// If suitable addressibility and definedness are not established, +// Valgrind prints an error message and returns the address of the first +// offending byte. Otherwise it returns zero. +check_mem_is_defined :: proc "c" (qzz: []byte) -> uintptr { + return mem_check_client_request_expr(0, .Check_Mem_Is_Defined, uintptr(raw_data(qzz)), uintptr(len(qzz)), 0, 0, 0) +} + +// Similar to `make_mem_defined(qzz)` except that addressability is not altered: +// bytes which are addressable are marked as defined, but those which +// are not addressable are left unchanged. +// Returns true when run on Valgrind and false otherwise. +make_mem_defined_if_addressable :: proc "c" (qzz: []byte) -> bool { + return 0 != mem_check_client_request_expr(0, .Make_Mem_Defined_If_Addressable, uintptr(raw_data(qzz)), uintptr(len(qzz)), 0, 0, 0) +} + +// Create a block-description handle. +// The description is an ascii string which is included in any messages +// pertaining to addresses within the specified memory range. +// Has no other effect on the properties of the memory range. +create_block :: proc "c" (qzz: []u8, desc: cstring) -> bool { + return 0 != mem_check_client_request_expr(0, .Create_Block, uintptr(raw_data(qzz)), uintptr(len(qzz)), uintptr(rawptr(desc)), 0, 0) +} +// Discard a block-description-handle. Returns true for an invalid handle, false for a valid handle. +discard :: proc "c" (blk_index: uintptr) -> bool { + return 0 != mem_check_client_request_expr(0, .Discard, 0, blk_index, 0, 0, 0) +} + + +// Do a full memory leak check (like `--leak-check=full`) mid-execution. +leak_check :: proc "c" () { + mem_check_client_request_stmt(.Do_Leak_Check, 0, 0, 0, 0, 0) +} +// Same as `leak_check()` but only showing the entries for which there was an increase +// in leaked bytes or leaked nr of blocks since the previous leak search. +added_leak_check :: proc "c" () { + mem_check_client_request_stmt(.Do_Leak_Check, 0, 1, 0, 0, 0) +} +// Same as `added_leak_check()` but showing entries with increased or decreased +// leaked bytes/blocks since previous leak search. +changed_leak_check :: proc "c" () { + mem_check_client_request_stmt(.Do_Leak_Check, 0, 2, 0, 0, 0) +} +// Do a summary memory leak check (like `--leak-check=summary`) mid-execution. +quick_leak_check :: proc "c" () { + mem_check_client_request_stmt(.Do_Leak_Check, 1, 0, 0, 0, 0) +} + +Count_Result :: struct { + leaked: uint, + dubious: uint, + reachable: uint, + suppressed: uint, +} + +count_leaks :: proc "c" () -> (res: Count_Result) { + mem_check_client_request_stmt( + .Count_Leaks, + uintptr(&res.leaked), + uintptr(&res.dubious), + uintptr(&res.reachable), + uintptr(&res.suppressed), + 0, + ) + return +} + +count_leak_blocks :: proc "c" () -> (res: Count_Result) { + mem_check_client_request_stmt( + .Count_Leak_Blocks, + uintptr(&res.leaked), + uintptr(&res.dubious), + uintptr(&res.reachable), + uintptr(&res.suppressed), + 0, + ) + return +} + +// Get the validity data for addresses zza and copy it +// into the provided zzvbits array. Return values: +// 0 - if not running on valgrind +// 1 - success +// 2 - [previously indicated unaligned arrays; these are now allowed] +// 3 - if any parts of zzsrc/zzvbits are not addressable. +// The metadata is not copied in cases 0, 2 or 3 so it should be +// impossible to segfault your system by using this call. +get_vbits :: proc(zza, zzvbits: []byte) -> u8 { + // assert requires a `context` thus these procedures cannot `proc "c"` + assert(len(zzvbits) >= len(zza)/8) + return u8(mem_check_client_request_expr(0, .Get_Vbits, uintptr(raw_data(zza)), uintptr(raw_data(zzvbits)), uintptr(len(zza)), 0, 0)) +} + +// Set the validity data for addresses zza, copying it +// from the provided zzvbits array. Return values: +// 0 - if not running on valgrind +// 1 - success +// 2 - [previously indicated unaligned arrays; these are now allowed] +// 3 - if any parts of zza/zzvbits are not addressable. +// The metadata is not copied in cases 0, 2 or 3 so it should be +// impossible to segfault your system by using this call. +set_vbits :: proc(zzvbits, zza: []byte) -> u8 { + // assert requires a `context` thus these procedures cannot `proc "c"` + assert(len(zzvbits) >= len(zza)/8) + return u8(mem_check_client_request_expr(0, .Set_Vbits, uintptr(raw_data(zza)), uintptr(raw_data(zzvbits)), uintptr(len(zza)), 0, 0)) +} + +// (Re-)enable reporting of addressing errors in the specified address range. +enable_addr_error_reporting_in_range :: proc "c" (qzz: []byte) -> uintptr { + return mem_check_client_request_expr(0, .Enable_Addr_Error_Reporting_In_Range, uintptr(raw_data(qzz)), uintptr(len(qzz)), 0, 0, 0) +} +// Disable reporting of addressing errors in the specified address range. +disable_addr_error_reporting_in_range :: proc "c" (qzz: []byte) -> uintptr { + return mem_check_client_request_expr(0, .Disable_Addr_Error_Reporting_In_Range, uintptr(raw_data(qzz)), uintptr(len(qzz)), 0, 0, 0) +} \ No newline at end of file diff --git a/core/sys/valgrind/valgrind.odin b/core/sys/valgrind/valgrind.odin new file mode 100644 index 000000000..2fb95588f --- /dev/null +++ b/core/sys/valgrind/valgrind.odin @@ -0,0 +1,182 @@ +//+build amd64 +package sys_valgrind + +import "core:intrinsics" + +Client_Request :: enum uintptr { + Running_On_Valgrind = 4097, + Discard_Translations = 4098, + Client_Call0 = 4353, + Client_Call1 = 4354, + Client_Call2 = 4355, + Client_Call3 = 4356, + Count_Errors = 4609, + Gdb_Monitor_Command = 4610, + Malloc_Like_Block = 4865, + Resize_Inplace_Block = 4875, + Free_Like_Block = 4866, + Create_Mem_Pool = 4867, + Destroy_Mem_Pool = 4868, + Mem_Pool_Alloc = 4869, + Mem_Pool_Free = 4870, + Mem_Pool_Trim = 4871, + Move_Mem_Pool = 4872, + Mem_Pool_Change = 4873, + Mem_Pool_Exists = 4874, + Printf = 5121, + Printf_Backtrace = 5122, + Printf_Valist_By_Ref = 5123, + Printf_Backtrace_Valist_By_Ref = 5124, + Stack_Register = 5377, + Stack_Deregister = 5378, + Stack_Change = 5379, + Load_Pdb_Debug_Info = 5633, + Map_Ip_To_Src_Loc = 5889, + Change_Err_Disablement = 6145, + Vex_Init_For_Iri = 6401, + Inner_Threads = 6402, +} + +@(require_results) +client_request_expr :: proc "c" (default: uintptr, request: Client_Request, a0, a1, a2, a3, a4: uintptr) -> uintptr { + return intrinsics.valgrind_client_request(default, uintptr(request), a0, a1, a2, a3, a4) +} +client_request_stmt :: proc "c" (request: Client_Request, a0, a1, a2, a3, a4: uintptr) { + _ = intrinsics.valgrind_client_request(0, uintptr(request), a0, a1, a2, a3, a4) +} + +// Returns the number of Valgrinds this code is running under +// 0 - running natively +// 1 - running under Valgrind +// 2 - running under Valgrind which is running under another Valgrind +running_on_valgrind :: proc "c" () -> uintptr { + return client_request_expr(0, .Running_On_Valgrind, 0, 0, 0, 0, 0) +} + +// Discard translation of code in the slice qzz. Useful if you are debugging a JIT-er or some such, +// since it provides a way to make sure valgrind will retranslate the invalidated area. +discard_translations :: proc "c" (qzz: []byte) { + client_request_stmt(.Discard_Translations, uintptr(raw_data(qzz)), uintptr(len(qzz)), 0, 0, 0) +} + +non_simd_call0 :: proc "c" (p: proc "c" (uintptr) -> uintptr) -> uintptr { + return client_request_expr(0, .Client_Call0, uintptr(rawptr(p)), 0, 0, 0, 0) +} +non_simd_call1 :: proc "c" (p: proc "c" (uintptr, uintptr) -> uintptr, a0: uintptr) -> uintptr { + return client_request_expr(0, .Client_Call1, uintptr(rawptr(p)), a0, 0, 0, 0) +} +non_simd_call2 :: proc "c" (p: proc "c" (uintptr, uintptr, uintptr) -> uintptr, a0, a1: uintptr) -> uintptr { + return client_request_expr(0, .Client_Call2, uintptr(rawptr(p)), a0, a1, 0, 0) +} +non_simd_call3 :: proc "c" (p: proc "c" (uintptr, uintptr, uintptr, uintptr) -> uintptr, a0, a1, a2: uintptr) -> uintptr { + return client_request_expr(0, .Client_Call3, uintptr(rawptr(p)), a0, a1, a2, 0) +} + +// Counts the number of errors that have been recorded by a tool. +count_errrors :: proc "c" () -> uint { + return uint(client_request_expr(0, .Count_Errors, 0, 0, 0, 0, 0)) +} + +monitor_command :: proc "c" (command: cstring) -> bool { + return 0 != client_request_expr(0, .Gdb_Monitor_Command, uintptr(rawptr(command)), 0, 0, 0, 0) +} + + +malloc_like_block :: proc "c" (mem: []byte, rz_b: uintptr, is_zeroed: bool) { + client_request_stmt(.Malloc_Like_Block, uintptr(raw_data(mem)), uintptr(len(mem)), rz_b, uintptr(is_zeroed), 0) +} +resize_inplace_block :: proc "c" (old_mem: []byte, new_size: uint, rz_b: uintptr) { + client_request_stmt(.Resize_Inplace_Block, uintptr(raw_data(old_mem)), uintptr(len(old_mem)), uintptr(new_size), rz_b, 0) +} +free_like_block :: proc "c" (addr: rawptr, rz_b: uintptr) { + client_request_stmt(.Free_Like_Block, uintptr(addr), rz_b, 0, 0, 0) +} + +Mem_Pool_Flags :: distinct bit_set[Mem_Pool_Flag; uintptr] +Mem_Pool_Flag :: enum uintptr { + Auto_Free = 0, + Meta_Pool = 1, +} + +// Create a memory pool. +create_mem_pool :: proc "c" (pool: rawptr, rz_b: uintptr, is_zeroed: bool, flags: Mem_Pool_Flags) { + client_request_stmt(.Create_Mem_Pool, uintptr(pool), rz_b, uintptr(is_zeroed), transmute(uintptr)flags, 0) +} +// Destroy a memory pool. +destroy_mem_pool :: proc "c" (pool: rawptr) { + client_request_stmt(.Destroy_Mem_Pool, uintptr(pool), 0, 0, 0, 0) +} +// Associate a section of memory with a memory pool. +mem_pool_alloc :: proc "c" (pool: rawptr, mem: []byte) { + client_request_stmt(.Mem_Pool_Alloc, uintptr(pool), uintptr(raw_data(mem)), uintptr(len(mem)), 0, 0) +} +// Disassociate a section of memory from a memory pool. +mem_pool_free :: proc "c" (pool: rawptr, addr: rawptr) { + client_request_stmt(.Mem_Pool_Free, uintptr(pool), uintptr(addr), 0, 0, 0) +} +// Disassociate parts of a section of memory outside a particular range. +mem_pool_trim :: proc "c" (pool: rawptr, mem: []byte) { + client_request_stmt(.Mem_Pool_Trim, uintptr(pool), uintptr(raw_data(mem)), uintptr(len(mem)), 0, 0) +} +// Resize and/or move a section of memory associated with a memory pool. +move_mem_pool :: proc "c" (pool_a, pool_b: rawptr) { + client_request_stmt(.Move_Mem_Pool, uintptr(pool_a), uintptr(pool_b), 0, 0, 0) +} +// Resize and/or move a section of memory associated with a memory pool. +mem_pool_change :: proc "c" (pool: rawptr, addr_a: rawptr, mem: []byte) { + client_request_stmt(.Mem_Pool_Change, uintptr(pool), uintptr(addr_a), uintptr(raw_data(mem)), uintptr(len(mem)), 0) +} +// Return true if a memory pool exists +mem_pool_exists :: proc "c" (pool: rawptr) -> bool { + return 0 != client_request_expr(0, .Mem_Pool_Exists, uintptr(pool), 0, 0, 0, 0) +} + + +// Mark a section of memory as being a stack. Returns a stack id. +stack_register :: proc "c" (stack: []byte) -> (stack_id: uintptr) { + ptr := uintptr(raw_data(stack)) + return client_request_expr(0, .Stack_Register, ptr, ptr+uintptr(len(stack)), 0, 0, 0) +} + +// Unmark a section of memory associated with a stack id as being a stack. +stack_deregister :: proc "c" (id: uintptr) { + client_request_stmt(.Stack_Deregister, id, 0, 0, 0, 0) +} + +// Change the start and end address of the stack id with the `new_stack` slice. +stack_change :: proc "c" (id: uint, new_stack: []byte) { + ptr := uintptr(raw_data(new_stack)) + client_request_stmt(.Stack_Change, uintptr(id), ptr, ptr + uintptr(len(new_stack)), 0, 0) +} + + +// Disable error reporting for the current thread/ +// It behaves in a stack-like way, meaning you can safely call this multiple times +// given that `enable_error_reporting()` is called the same number of times to +// re-enable the error reporting. +// The first call of this macro disables reporting. +// Subsequent calls have no effect except to increase the number of `enable_error_reporting()` +// calls needed to re-enable reporting. +// Child threads do not inherit this setting from their parents; +// they are always created with reporting enabled. +disable_error_reporting :: proc "c" () { + client_request_stmt(.Change_Err_Disablement, 1, 0, 0, 0, 0) +} +// Re-enable error reporting +enable_error_reporting :: proc "c" () { + client_request_stmt(.Change_Err_Disablement, ~uintptr(0), 0, 0, 0, 0) +} + + +inner_threads :: proc "c" (qzz: rawptr) { + client_request_stmt(.Inner_Threads, uintptr(qzz), 0, 0, 0, 0) +} + + +// Map a code address to a source file name and line number. +// `buf64` must point to a 64-byte buffer in the caller's address space. +// The result will be dumped in there and is guaranteed to be zero terminated. +// If no info is found, the first byte is set to zero. +map_ip_to_src_loc :: proc "c" (addr: rawptr, buf64: ^[64]byte) -> uintptr { + return client_request_expr(0, .Map_Ip_To_Src_Loc, uintptr(addr), uintptr(buf64), 0, 0, 0) +} \ No newline at end of file