mirror of
https://github.com/odin-lang/Odin.git
synced 2026-07-31 10:17:44 -04:00
335 lines
8.0 KiB
Odin
335 lines
8.0 KiB
Odin
#+vet explicit-allocators
|
|
package debug_trace
|
|
|
|
import "base:runtime"
|
|
|
|
import "core:fmt"
|
|
import "core:mem"
|
|
import "core:sync"
|
|
|
|
/*
|
|
The backtrace tracking allocator is a similar allocator as the `core:mem` tracking allocator but keeps
|
|
backtraces for each allocation.
|
|
|
|
Print results at the end using `tracking_allocator_print_results`.
|
|
|
|
Example:
|
|
package main
|
|
|
|
import "core:debug/trace"
|
|
|
|
main :: proc() {
|
|
track: trace.Tracking_Allocator
|
|
trace.tracking_allocator_init(&track, context.allocator)
|
|
defer trace.tracking_allocator_destroy(&track)
|
|
|
|
context.allocator = trace.tracking_allocator(&track)
|
|
defer trace.tracking_allocator_print_results(&track)
|
|
|
|
_main()
|
|
}
|
|
|
|
_main :: proc() {
|
|
for _ in 0..<5 {
|
|
_ = new(int)
|
|
free(rawptr(uintptr(100)))
|
|
}
|
|
}
|
|
*/
|
|
Tracking_Allocator :: struct {
|
|
backing: mem.Allocator,
|
|
allocation_map: map[rawptr]Tracking_Allocator_Entry,
|
|
bad_free_callback: Tracking_Allocator_Bad_Free_Callback,
|
|
bad_free_array: [dynamic]Tracking_Allocator_Bad_Free_Entry,
|
|
mutex: sync.Mutex,
|
|
clear_on_free_all: bool,
|
|
total_memory_allocated: i64,
|
|
total_allocation_count: i64,
|
|
total_memory_freed: i64,
|
|
total_free_count: i64,
|
|
peak_memory_allocated: i64,
|
|
current_memory_allocated: i64,
|
|
}
|
|
|
|
Tracking_Allocator_Entry :: struct {
|
|
memory: rawptr,
|
|
size: int,
|
|
alignment: int,
|
|
mode: mem.Allocator_Mode,
|
|
err: mem.Allocator_Error,
|
|
location: runtime.Source_Code_Location,
|
|
backtrace: Capture_Const,
|
|
}
|
|
|
|
Tracking_Allocator_Bad_Free_Entry :: struct {
|
|
memory: rawptr,
|
|
location: runtime.Source_Code_Location,
|
|
backtrace: Capture_Const,
|
|
}
|
|
|
|
/*
|
|
Callback type for when tracking allocator runs into a bad free.
|
|
*/
|
|
Tracking_Allocator_Bad_Free_Callback :: #type proc(
|
|
t: ^Tracking_Allocator,
|
|
memory: rawptr,
|
|
backtrace: Capture_Const,
|
|
location: runtime.Source_Code_Location,
|
|
)
|
|
|
|
@(no_sanitize_address)
|
|
tracking_allocator_init :: proc(
|
|
t: ^Tracking_Allocator,
|
|
backing_allocator: mem.Allocator,
|
|
internals_allocator := context.allocator,
|
|
) {
|
|
t.backing = backing_allocator
|
|
t.allocation_map.allocator = internals_allocator
|
|
t.bad_free_callback = tracking_allocator_bad_free_callback_panic
|
|
t.bad_free_array.allocator = internals_allocator
|
|
|
|
if .Free_All in mem.query_features(t.backing) {
|
|
t.clear_on_free_all = true
|
|
}
|
|
}
|
|
|
|
@(no_sanitize_address)
|
|
tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) {
|
|
delete(t.allocation_map)
|
|
delete(t.bad_free_array)
|
|
}
|
|
|
|
@(no_sanitize_address)
|
|
tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
|
|
sync.guard(&t.mutex)
|
|
|
|
clear(&t.allocation_map)
|
|
clear(&t.bad_free_array)
|
|
t.current_memory_allocated = 0
|
|
}
|
|
|
|
@(no_sanitize_address)
|
|
tracking_allocator_reset :: proc(t: ^Tracking_Allocator) {
|
|
sync.guard(&t.mutex)
|
|
clear(&t.allocation_map)
|
|
clear(&t.bad_free_array)
|
|
t.total_memory_allocated = 0
|
|
t.total_allocation_count = 0
|
|
t.total_memory_freed = 0
|
|
t.total_free_count = 0
|
|
t.peak_memory_allocated = 0
|
|
t.current_memory_allocated = 0
|
|
}
|
|
|
|
@(no_sanitize_address)
|
|
tracking_allocator_bad_free_callback_panic :: proc(
|
|
t: ^Tracking_Allocator,
|
|
memory: rawptr,
|
|
backtrace: Capture_Const,
|
|
location: runtime.Source_Code_Location,
|
|
) {
|
|
buf: [256]byte
|
|
offset := 0
|
|
_ = runtime.write_string(&offset, buf[:], "Tracking allocator error: Bad free of pointer ")
|
|
_ = runtime.write_u64(&offset, buf[:], u64(uintptr(memory)))
|
|
panic(string(buf[:offset]), loc = location)
|
|
}
|
|
|
|
@(no_sanitize_address)
|
|
tracking_allocator_bad_free_callback_add_to_array :: proc(
|
|
t: ^Tracking_Allocator,
|
|
memory: rawptr,
|
|
backtrace: Capture_Const,
|
|
location: runtime.Source_Code_Location,
|
|
) {
|
|
append(&t.bad_free_array, Tracking_Allocator_Bad_Free_Entry {
|
|
memory = memory,
|
|
location = location,
|
|
backtrace = backtrace,
|
|
})
|
|
}
|
|
|
|
@(require_results, no_sanitize_address)
|
|
tracking_allocator :: proc(data: ^Tracking_Allocator) -> mem.Allocator {
|
|
return mem.Allocator{data = data, procedure = tracking_allocator_proc}
|
|
}
|
|
|
|
@(no_sanitize_address)
|
|
tracking_allocator_proc :: proc(
|
|
allocator_data: rawptr,
|
|
mode: mem.Allocator_Mode,
|
|
size, alignment: int,
|
|
old_memory: rawptr,
|
|
old_size: int,
|
|
loc := #caller_location,
|
|
) -> (
|
|
result: []byte,
|
|
err: mem.Allocator_Error,
|
|
) {
|
|
@(no_sanitize_address)
|
|
track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
|
|
data.total_memory_allocated += i64(entry.size)
|
|
data.total_allocation_count += 1
|
|
data.current_memory_allocated += i64(entry.size)
|
|
if data.current_memory_allocated > data.peak_memory_allocated {
|
|
data.peak_memory_allocated = data.current_memory_allocated
|
|
}
|
|
}
|
|
|
|
@(no_sanitize_address)
|
|
track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
|
|
data.total_memory_freed += i64(entry.size)
|
|
data.total_free_count += 1
|
|
data.current_memory_allocated -= i64(entry.size)
|
|
}
|
|
|
|
data := (^Tracking_Allocator)(allocator_data)
|
|
|
|
sync.mutex_guard(&data.mutex)
|
|
|
|
if mode == .Query_Info {
|
|
info := (^mem.Allocator_Query_Info)(old_memory)
|
|
if info != nil && info.pointer != nil {
|
|
if entry, ok := data.allocation_map[info.pointer]; ok {
|
|
info.size = entry.size
|
|
info.alignment = entry.alignment
|
|
}
|
|
info.pointer = nil
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if mode == .Free && old_memory != nil && old_memory not_in data.allocation_map {
|
|
if data.bad_free_callback != nil {
|
|
data.bad_free_callback(data, old_memory, capture(skip=1), loc)
|
|
}
|
|
} else {
|
|
result = data.backing.procedure(
|
|
data.backing.data,
|
|
mode,
|
|
size,
|
|
alignment,
|
|
old_memory,
|
|
old_size,
|
|
loc,
|
|
) or_return
|
|
}
|
|
result_ptr := raw_data(result)
|
|
|
|
if data.allocation_map.allocator.procedure == nil {
|
|
data.allocation_map.allocator = context.allocator
|
|
}
|
|
|
|
switch mode {
|
|
case .Alloc, .Alloc_Non_Zeroed:
|
|
data.allocation_map[result_ptr] = Tracking_Allocator_Entry {
|
|
memory = result_ptr,
|
|
size = size,
|
|
mode = mode,
|
|
alignment = alignment,
|
|
err = err,
|
|
location = loc,
|
|
backtrace = capture(skip=1),
|
|
}
|
|
track_alloc(data, &data.allocation_map[result_ptr])
|
|
case .Free:
|
|
if old_memory != nil && old_memory in data.allocation_map {
|
|
track_free(data, &data.allocation_map[old_memory])
|
|
}
|
|
delete_key(&data.allocation_map, old_memory)
|
|
case .Free_All:
|
|
if data.clear_on_free_all {
|
|
clear_map(&data.allocation_map)
|
|
data.current_memory_allocated = 0
|
|
}
|
|
case .Resize, .Resize_Non_Zeroed:
|
|
if old_memory != nil && old_memory in data.allocation_map {
|
|
track_free(data, &data.allocation_map[old_memory])
|
|
}
|
|
if old_memory != result_ptr {
|
|
delete_key(&data.allocation_map, old_memory)
|
|
}
|
|
data.allocation_map[result_ptr] = Tracking_Allocator_Entry {
|
|
memory = result_ptr,
|
|
size = size,
|
|
mode = mode,
|
|
alignment = alignment,
|
|
err = err,
|
|
location = loc,
|
|
backtrace = capture(skip=1),
|
|
}
|
|
track_alloc(data, &data.allocation_map[result_ptr])
|
|
|
|
case .Query_Features:
|
|
set := (^mem.Allocator_Mode_Set)(old_memory)
|
|
if set != nil {
|
|
set^ = {
|
|
.Alloc,
|
|
.Alloc_Non_Zeroed,
|
|
.Free,
|
|
.Free_All,
|
|
.Resize,
|
|
.Query_Features,
|
|
.Query_Info,
|
|
}
|
|
}
|
|
return nil, nil
|
|
|
|
case .Query_Info:
|
|
unreachable()
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
tracking_allocator_print_results :: proc(t: ^Tracking_Allocator, temp_allocator := context.temp_allocator) {
|
|
i: int
|
|
ALLOCATOR_MAX_BACKTRACES :: 16
|
|
|
|
for _, leak in t.allocation_map {
|
|
fmt.eprintfln("%v leaked %m", leak.location, leak.size)
|
|
|
|
defer i += 1
|
|
if i > ALLOCATOR_MAX_BACKTRACES {
|
|
continue
|
|
}
|
|
|
|
fmt.eprintln("[back trace]")
|
|
|
|
trace, err := resolve(leak.backtrace, temp_allocator, temp_allocator)
|
|
if err != nil {
|
|
fmt.eprintfln("\tbacktrace error: %v", err)
|
|
continue
|
|
}
|
|
defer locations_destroy(trace, temp_allocator)
|
|
|
|
print(trace)
|
|
fmt.eprintln()
|
|
}
|
|
|
|
for bad_free, _ in t.bad_free_array {
|
|
fmt.eprintfln(
|
|
"%v allocation %p was freed badly",
|
|
bad_free.location,
|
|
bad_free.memory,
|
|
)
|
|
|
|
defer i += 1
|
|
if i > ALLOCATOR_MAX_BACKTRACES {
|
|
continue
|
|
}
|
|
|
|
fmt.eprintln("[back trace]")
|
|
|
|
trace, err := resolve(bad_free.backtrace, temp_allocator, temp_allocator)
|
|
if err != nil {
|
|
fmt.eprintfln("\tbacktrace error: %v", err)
|
|
continue
|
|
}
|
|
defer locations_destroy(trace, temp_allocator)
|
|
|
|
print(trace)
|
|
}
|
|
}
|