Removed back
This commit is contained in:
parent
94e9860a25
commit
5b0cdd5978
16 changed files with 1 additions and 1319 deletions
|
|
@ -10,7 +10,7 @@ if [[ -v 1 ]]; then
|
||||||
ODIN_FLAGS="-debug"
|
ODIN_FLAGS="-debug"
|
||||||
elif [ "$1" = "RELEASE" ]; then
|
elif [ "$1" = "RELEASE" ]; then
|
||||||
# unsure if this will work
|
# unsure if this will work
|
||||||
ODIN_FLAGS="-o:size"
|
ODIN_FLAGS="-o:minimal"
|
||||||
else
|
else
|
||||||
echo "Usage: <script> <DEBUG/RELEASE>"
|
echo "Usage: <script> <DEBUG/RELEASE>"
|
||||||
exit 1
|
exit 1
|
||||||
|
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
Copyright (c) 2023 Laytan Laats
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
@ -1,188 +0,0 @@
|
||||||
# Back
|
|
||||||
|
|
||||||
Backtraces for Odin, see examples below and in the examples folder.
|
|
||||||
|
|
||||||
To change the size (amount of stackframes to print) in places where this can't be set directly, you can use the `-define:BACKTRACE_SIZE=16`.
|
|
||||||
|
|
||||||
## Targets
|
|
||||||
|
|
||||||
### Windows, MacoOS & Linux
|
|
||||||
|
|
||||||
These targets use debug information that's added by compiling with `-debug` to provide traces and give rough information when compiled without it.
|
|
||||||
|
|
||||||
Performance is not impacted at all during normal running, just when you ask for a trace or lines.
|
|
||||||
|
|
||||||
Per platform notes below:
|
|
||||||
|
|
||||||
#### Windows
|
|
||||||
|
|
||||||
For Windows support, all credit goes to [DaseinPhaos/pdb](https://github.com/DaseinPhaos/pdb).
|
|
||||||
|
|
||||||
Windows is not able to get *any* information when not compiled with `-debug`.
|
|
||||||
|
|
||||||
NOTE: The pdb package allocates a lot of stuff and does not really provide a way of deleting the allocations, so, before calling into the package, this package sets it to use the `context.temporary_allocator`.
|
|
||||||
|
|
||||||
#### MacOS
|
|
||||||
|
|
||||||
Uses a private framework for symbolication, and thus will not get through Apple's review process.
|
|
||||||
|
|
||||||
#### Linux
|
|
||||||
|
|
||||||
On Linux, the `addr2line` command is invoked, which comes pre-installed (maybe in binutils).
|
|
||||||
|
|
||||||
The `addr2line` command can be changed by setting the `-define:BACK_ADDR2LINE_PATH=your/atos` flag.
|
|
||||||
|
|
||||||
The path to the running binary is also needed for this command, this is `os.args[0]` by default and to my knowledge is always correct.
|
|
||||||
Nevertheless it can be changed with the `-define:BACK_PROGRAM=path/to/binary` flag.
|
|
||||||
|
|
||||||
I am planning on rewriting this implementation to not require an external command like this by parsing the DWARF debug information manually.
|
|
||||||
|
|
||||||
### Others
|
|
||||||
|
|
||||||
Other targets use the instrumentation features of Odin to keep track of stack frames, this has a minimal impact on performance and size.
|
|
||||||
|
|
||||||
Forcing the instrumentation based implementation on Windows, Linux and Darwin can be done with `-define:BACK_FORCE_FALLBACK=true`.
|
|
||||||
|
|
||||||
NOTE: this implementation requires at least `-o:minimal` as it requires `#force_inline` procs to actually be inlined, this is not the case with `-o:none`.
|
|
||||||
|
|
||||||
NOTE: the instrumentation features and WASM combination is a bit fragile and seems to only work on `-o:minimal` exclusively, this is almost certainly a codegen bug.
|
|
||||||
|
|
||||||
## Manual
|
|
||||||
|
|
||||||
```odin
|
|
||||||
package manual
|
|
||||||
|
|
||||||
import "core:fmt"
|
|
||||||
|
|
||||||
import back "../.."
|
|
||||||
|
|
||||||
main :: proc() {
|
|
||||||
// Allocates for 16 frames.
|
|
||||||
bt := back.trace_n(16)
|
|
||||||
print(bt)
|
|
||||||
|
|
||||||
// Or, doesn't allocate, returns `-define:BACKTRACE_SIZE` frames.
|
|
||||||
btc := back.trace()
|
|
||||||
print(btc.trace[:btc.len])
|
|
||||||
|
|
||||||
// Or, fill in a slice.
|
|
||||||
bt = make(back.Trace, 16)
|
|
||||||
bt = bt[:back.trace_fill(bt)]
|
|
||||||
print(bt)
|
|
||||||
}
|
|
||||||
|
|
||||||
print :: proc(bt: back.Trace) {
|
|
||||||
lines, err := back.lines(bt)
|
|
||||||
if err != nil {
|
|
||||||
fmt.eprintf("Could not retrieve backtrace lines: %v\n", err)
|
|
||||||
} else {
|
|
||||||
defer back.lines_destroy(lines)
|
|
||||||
|
|
||||||
fmt.eprintln("[back trace]")
|
|
||||||
back.print(lines)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// [back trace]
|
|
||||||
// back.trace_n - /Users/laytan/projects/back/back.odin:59
|
|
||||||
// manual.main - /Users/laytan/projects/back/examples/manual/main.odin:9
|
|
||||||
// main - /Users/laytan/third-party/Odin/core/runtime/entry_unix.odin:53
|
|
||||||
// [back trace]
|
|
||||||
// back.trace - /Users/laytan/projects/back/back.odin:52
|
|
||||||
// manual.main - /Users/laytan/projects/back/examples/manual/main.odin:13
|
|
||||||
// main - /Users/laytan/third-party/Odin/core/runtime/entry_unix.odin:53
|
|
||||||
// [back trace]
|
|
||||||
// back.trace_fill - /Users/laytan/projects/back/back.odin:64
|
|
||||||
// manual.main - /Users/laytan/projects/back/examples/manual/main.odin:18
|
|
||||||
// main - /Users/laytan/third-party/Odin/core/runtime/entry_unix.odin:53
|
|
||||||
```
|
|
||||||
|
|
||||||
## Tracking Allocator
|
|
||||||
|
|
||||||
```odin
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "back"
|
|
||||||
|
|
||||||
_main :: proc() {
|
|
||||||
_ = new(int)
|
|
||||||
free(rawptr(uintptr(100)))
|
|
||||||
}
|
|
||||||
|
|
||||||
main :: proc() {
|
|
||||||
track: back.Tracking_Allocator
|
|
||||||
back.tracking_allocator_init(&track, context.allocator)
|
|
||||||
defer back.tracking_allocator_destroy(&track)
|
|
||||||
|
|
||||||
context.allocator = back.tracking_allocator(&track)
|
|
||||||
defer back.tracking_allocator_print_results(&track)
|
|
||||||
|
|
||||||
_main()
|
|
||||||
}
|
|
||||||
|
|
||||||
// /Users/laytan/projects/back/examples/allocator/main.odin(6:6) leaked 8b
|
|
||||||
// [back trace]
|
|
||||||
// back.trace - /Users/laytan/projects/back/back.odin:52
|
|
||||||
// back.tracking_allocator_proc - /Users/laytan/projects/back/allocator.odin:129
|
|
||||||
// runtime.mem_alloc_bytes - /Users/laytan/third-party/Odin/core/runtime/internal.odin:141
|
|
||||||
// runtime.new_aligned-13136 - /Users/laytan/third-party/Odin/core/runtime/core_builtin.odin:250
|
|
||||||
// runtime.new-13097 - /Users/laytan/third-party/Odin/core/runtime/core_builtin.odin:246
|
|
||||||
// main._main - /Users/laytan/projects/back/examples/allocator/main.odin:6
|
|
||||||
// main.main - /Users/laytan/projects/back/examples/allocator/main.odin:18
|
|
||||||
// main - /Users/laytan/third-party/Odin/core/runtime/entry_unix.odin:53
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// /Users/laytan/projects/back/examples/allocator/main.odin(7:2) allocation 64 was freed badly
|
|
||||||
// [back trace]
|
|
||||||
// back.trace - /Users/laytan/projects/back/back.odin:52
|
|
||||||
// back.tracking_allocator_proc - /Users/laytan/projects/back/allocator.odin:102
|
|
||||||
// runtime.mem_free - /Users/laytan/third-party/Odin/core/runtime/internal.odin:162
|
|
||||||
// main._main - /Users/laytan/projects/back/examples/allocator/main.odin:8
|
|
||||||
// main.main - /Users/laytan/projects/back/examples/allocator/main.odin:18
|
|
||||||
// main - /Users/laytan/third-party/Odin/core/runtime/entry_unix.odin:53
|
|
||||||
```
|
|
||||||
|
|
||||||
## Printing a backtrace on assertion failures / panics
|
|
||||||
|
|
||||||
```odin
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "back"
|
|
||||||
|
|
||||||
main :: proc() {
|
|
||||||
context.assertion_failure_proc = back.assertion_failure_proc
|
|
||||||
assert(3 == 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// [back trace]
|
|
||||||
// back.trace - /Users/laytan/projects/back/back.odin:52
|
|
||||||
// back.assertion_failure_proc - /Users/laytan/projects/back/back.odin:97
|
|
||||||
// runtime.assert.internal-0 - /Users/laytan/third-party/Odin/core/runtime/core_builtin.odin:818
|
|
||||||
// runtime.assert - /Users/laytan/third-party/Odin/core/runtime/core_builtin.odin:820
|
|
||||||
// main.main - /Users/laytan/projects/back/examples/assert_backtrace/main.odin:8
|
|
||||||
// main - /Users/laytan/third-party/Odin/core/runtime/entry_unix.odin:53
|
|
||||||
// /Users/laytan/projects/back/examples/assert_backtrace/main.odin(7:5) runtime assertion
|
|
||||||
```
|
|
||||||
|
|
||||||
## Printing a backtrace on segmentation faults
|
|
||||||
|
|
||||||
```odin
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "back"
|
|
||||||
|
|
||||||
main :: proc() {
|
|
||||||
back.register_segfault_handler()
|
|
||||||
|
|
||||||
ptr: ^int
|
|
||||||
bad := ptr^ + 2
|
|
||||||
_ = bad
|
|
||||||
}
|
|
||||||
|
|
||||||
// Segmentation Fault
|
|
||||||
// [back trace]
|
|
||||||
// back.trace - /Users/laytan/projects/back/back.odin:52
|
|
||||||
// back.register_segfault_handler$anon-1 - /Users/laytan/projects/back/back.odin:114
|
|
||||||
// ?? - ??
|
|
||||||
// main.main - /Users/laytan/projects/back/examples/segfault/main.odin:8
|
|
||||||
```
|
|
||||||
|
|
@ -1,230 +0,0 @@
|
||||||
#+vet explicit-allocators
|
|
||||||
package back
|
|
||||||
|
|
||||||
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.
|
|
||||||
//
|
|
||||||
// See examples/allocator for a usage snippet.
|
|
||||||
//
|
|
||||||
// Print results at the end using tracking_allocator_print_results().
|
|
||||||
Tracking_Allocator :: struct {
|
|
||||||
backing: mem.Allocator,
|
|
||||||
internals_allocator: mem.Allocator,
|
|
||||||
allocation_map: map[rawptr]Tracking_Allocator_Entry,
|
|
||||||
bad_free_array: [dynamic]Tracking_Allocator_Bad_Free_Entry,
|
|
||||||
mutex: sync.Mutex,
|
|
||||||
clear_on_free_all: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
Tracking_Allocator_Entry :: struct {
|
|
||||||
memory: rawptr,
|
|
||||||
size: int,
|
|
||||||
alignment: int,
|
|
||||||
mode: mem.Allocator_Mode,
|
|
||||||
err: mem.Allocator_Error,
|
|
||||||
location: runtime.Source_Code_Location,
|
|
||||||
backtrace: Trace_Const,
|
|
||||||
}
|
|
||||||
|
|
||||||
Tracking_Allocator_Bad_Free_Entry :: struct {
|
|
||||||
memory: rawptr,
|
|
||||||
location: runtime.Source_Code_Location,
|
|
||||||
backtrace: Trace_Const,
|
|
||||||
}
|
|
||||||
|
|
||||||
tracking_allocator_init :: proc(
|
|
||||||
t: ^Tracking_Allocator,
|
|
||||||
backing_allocator: mem.Allocator,
|
|
||||||
internals_allocator := context.allocator,
|
|
||||||
) {
|
|
||||||
t.backing = backing_allocator
|
|
||||||
t.internals_allocator = internals_allocator
|
|
||||||
t.allocation_map.allocator = internals_allocator
|
|
||||||
t.bad_free_array.allocator = internals_allocator
|
|
||||||
|
|
||||||
if .Free_All in mem.query_features(t.backing) {
|
|
||||||
t.clear_on_free_all = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) {
|
|
||||||
delete(t.allocation_map)
|
|
||||||
delete(t.bad_free_array)
|
|
||||||
}
|
|
||||||
|
|
||||||
tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
|
|
||||||
sync.guard(&t.mutex)
|
|
||||||
|
|
||||||
clear(&t.allocation_map)
|
|
||||||
clear(&t.bad_free_array)
|
|
||||||
}
|
|
||||||
|
|
||||||
@(require_results)
|
|
||||||
tracking_allocator :: proc(data: ^Tracking_Allocator) -> mem.Allocator {
|
|
||||||
return mem.Allocator{data = data, procedure = tracking_allocator_proc}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
) {
|
|
||||||
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 {
|
|
||||||
append(
|
|
||||||
&data.bad_free_array,
|
|
||||||
Tracking_Allocator_Bad_Free_Entry{
|
|
||||||
memory = old_memory,
|
|
||||||
location = loc,
|
|
||||||
backtrace = trace(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} 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 = trace(),
|
|
||||||
}
|
|
||||||
case .Free:
|
|
||||||
delete_key(&data.allocation_map, old_memory)
|
|
||||||
case .Free_All:
|
|
||||||
if data.clear_on_free_all {
|
|
||||||
clear_map(&data.allocation_map)
|
|
||||||
}
|
|
||||||
case .Resize, .Resize_Non_Zeroed:
|
|
||||||
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 = trace(),
|
|
||||||
}
|
|
||||||
|
|
||||||
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("\x1b[31m%v leaked %m\x1b[0m", leak.location, leak.size)
|
|
||||||
|
|
||||||
defer i += 1
|
|
||||||
if i > ALLOCATOR_MAX_BACKTRACES {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
trace, err := lines(leak.backtrace, temp_allocator, temp_allocator)
|
|
||||||
defer lines_destroy(trace, temp_allocator)
|
|
||||||
|
|
||||||
fmt.eprintln("[back trace]")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.eprintfln("backtrace error: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
print(trace, temp_allocator=temp_allocator)
|
|
||||||
fmt.eprintln()
|
|
||||||
}
|
|
||||||
|
|
||||||
for bad_free, _ in t.bad_free_array {
|
|
||||||
fmt.eprintfln(
|
|
||||||
"\x1b[31m%v allocation %p was freed badly\x1b[0m",
|
|
||||||
bad_free.location,
|
|
||||||
bad_free.memory,
|
|
||||||
)
|
|
||||||
|
|
||||||
defer i += 1
|
|
||||||
if i > ALLOCATOR_MAX_BACKTRACES {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
trace, err := lines(bad_free.backtrace, temp_allocator, temp_allocator)
|
|
||||||
defer lines_destroy(trace, temp_allocator)
|
|
||||||
|
|
||||||
fmt.eprintln("[back trace]")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.eprintf("backtrace error: %v\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
print(trace, temp_allocator=temp_allocator)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
#+vet explicit-allocators
|
|
||||||
package back
|
|
||||||
|
|
||||||
import "base:runtime"
|
|
||||||
|
|
||||||
import "core:fmt"
|
|
||||||
import "core:io"
|
|
||||||
import "core:os"
|
|
||||||
import "core:sync"
|
|
||||||
import "core:text/table"
|
|
||||||
|
|
||||||
// Size of a constant backtrace, as used by the tracking allocator for example.
|
|
||||||
BACKTRACE_SIZE :: #config(BACKTRACE_SIZE, 16)
|
|
||||||
|
|
||||||
// For targets that do not have native support (using debug info),
|
|
||||||
// backtraces are done through instrumentation, Odin only allows one enter/exit instrumentation
|
|
||||||
// procedure though, so you can set this to true, add your own instrumentation procs, and have
|
|
||||||
// them call `back.other_instrumentation_enter` and `back.other_instrumentation_exit` to hook
|
|
||||||
// up the backtraces.
|
|
||||||
// The custom proc must have `#force_inline`.
|
|
||||||
OTHER_CUSTOM_INSTRUMENTATION :: #config(BACK_OTHER_CUSTOM_INSTRUMENTATION, false)
|
|
||||||
|
|
||||||
// Force the fallback instrumentation based implementation instead of debug info based.
|
|
||||||
FORCE_FALLBACK :: #config(BACK_FORCE_FALLBACK, false)
|
|
||||||
|
|
||||||
// Fallback requires a single module (subtle bugs with multiple modules and instrumentation in Odin),
|
|
||||||
// and at least -o:minimal (#force_inline has to actually inline).
|
|
||||||
_COULD_USE_FALLBACK_WITHOUT_ERROR :: !ODIN_USE_SEPARATE_MODULES && ODIN_OPTIMIZATION_MODE >= .Minimal
|
|
||||||
|
|
||||||
// Use the fallback (instrumentation based) implementation:
|
|
||||||
// if it is forced, or it can be used without error and debug info is off, or if the target has no debug info based support.
|
|
||||||
USE_FALLBACK :: FORCE_FALLBACK || (_COULD_USE_FALLBACK_WITHOUT_ERROR && !ODIN_DEBUG) || (ODIN_OS != .Darwin && ODIN_OS != .Linux && ODIN_OS != .Windows)
|
|
||||||
|
|
||||||
ADDR2LINE_PATH :: #config(TRACE_ADDR2LINE_PATH, "addr2line")
|
|
||||||
|
|
||||||
Trace :: []Trace_Entry
|
|
||||||
|
|
||||||
Trace_Const :: struct {
|
|
||||||
trace: [BACKTRACE_SIZE]Trace_Entry,
|
|
||||||
len: int,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Platform specific.
|
|
||||||
Trace_Entry :: _Trace_Entry
|
|
||||||
|
|
||||||
Line :: struct {
|
|
||||||
location: string,
|
|
||||||
symbol: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: improve errors.
|
|
||||||
Lines_Error :: enum {
|
|
||||||
None,
|
|
||||||
Parse_Address_Fail,
|
|
||||||
Addr2line_Unexpected_EOF,
|
|
||||||
Addr2line_Output_Error,
|
|
||||||
Addr2line_Unresolved,
|
|
||||||
Addr2line_Process_Error,
|
|
||||||
Out_Of_Memory,
|
|
||||||
Info_Not_Found,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: arbitrary skip (argument).
|
|
||||||
|
|
||||||
trace :: #force_no_inline proc() -> (bt: Trace_Const) {
|
|
||||||
bt.len = _trace(bt.trace[:])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
trace_n :: #force_no_inline proc(max_len: i32, allocator := context.allocator) -> Trace {
|
|
||||||
bt := make([]Trace_Entry, max_len, allocator)
|
|
||||||
n := _trace(bt[:])
|
|
||||||
return bt[:n]
|
|
||||||
}
|
|
||||||
|
|
||||||
trace_fill :: #force_no_inline proc(buf: Trace) -> int {
|
|
||||||
return _trace(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
trace_n_destroy :: proc(b: Trace, allocator := context.allocator) {
|
|
||||||
delete(b, allocator)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Processes the message trying to get more/useful information.
|
|
||||||
// This adds file and line information if the program is running in debug mode.
|
|
||||||
//
|
|
||||||
// If an error is returned the original message will be the result and is save to use.
|
|
||||||
lines :: proc {
|
|
||||||
lines_n,
|
|
||||||
lines_const,
|
|
||||||
}
|
|
||||||
|
|
||||||
lines_n :: proc(bt: Trace, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (out: []Line, err: Lines_Error) {
|
|
||||||
return _lines(bt, allocator, temp_allocator)
|
|
||||||
}
|
|
||||||
|
|
||||||
lines_const :: proc(bt: Trace_Const, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (out: []Line, err: Lines_Error) {
|
|
||||||
bt := bt
|
|
||||||
return _lines(bt.trace[:bt.len], allocator, temp_allocator)
|
|
||||||
}
|
|
||||||
|
|
||||||
lines_destroy :: proc(lines: []Line, allocator := context.allocator) {
|
|
||||||
_lines_destroy(lines, allocator)
|
|
||||||
}
|
|
||||||
|
|
||||||
assertion_failure_proc :: proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! {
|
|
||||||
{
|
|
||||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
|
||||||
|
|
||||||
lines, err := lines(trace(), context.temp_allocator, context.temp_allocator)
|
|
||||||
if err != nil {
|
|
||||||
fmt.eprintf("could not get backtrace for assertion failure: %v\n", err)
|
|
||||||
} else {
|
|
||||||
fmt.eprintln("[back trace]")
|
|
||||||
print(lines, temp_allocator=context.temp_allocator)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.default_assertion_failure_proc(prefix, message, loc)
|
|
||||||
}
|
|
||||||
|
|
||||||
register_segfault_handler :: proc() {
|
|
||||||
_register_segfault_handler()
|
|
||||||
}
|
|
||||||
|
|
||||||
print :: proc(lines: []Line, padding := " ", w: Maybe(io.Writer) = nil, temp_allocator := context.temp_allocator) {
|
|
||||||
w := w.? or_else os.to_writer(os.stderr)
|
|
||||||
|
|
||||||
tbl := table.init(&table.Table{}, temp_allocator, temp_allocator)
|
|
||||||
|
|
||||||
for line in lines {
|
|
||||||
table.row(tbl, padding, line.symbol, " - ", line.location)
|
|
||||||
}
|
|
||||||
|
|
||||||
table.build(tbl, table.unicode_width_proc)
|
|
||||||
|
|
||||||
for row in 0..<tbl.nr_rows {
|
|
||||||
for col in 0..<tbl.nr_cols {
|
|
||||||
table.write_table_cell(w, tbl, row, col)
|
|
||||||
}
|
|
||||||
io.write_byte(w, '\n')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The dbghelp library of win32 is not thread safe, this library uses this mutex to get exclusive access.
|
|
||||||
// It is provided in case you want to use the dbghelp library, and want to coordinate access with this package.
|
|
||||||
_win32_dbghelp_mutex: sync.Mutex
|
|
||||||
|
|
@ -1,160 +0,0 @@
|
||||||
#+vet explicit-allocators
|
|
||||||
#+private file
|
|
||||||
package back
|
|
||||||
|
|
||||||
@require import "base:runtime"
|
|
||||||
|
|
||||||
@require import "core:strings"
|
|
||||||
@require import "core:sys/posix"
|
|
||||||
|
|
||||||
when !USE_FALLBACK {
|
|
||||||
|
|
||||||
foreign import system "system:System.framework"
|
|
||||||
|
|
||||||
// NOTE: CoreSymbolication is a private framework, Apple is allowed to break it and doesn't provide
|
|
||||||
// headers, although the API has as of my knowledge been the same in the past 10 years at least.
|
|
||||||
@(extra_linker_flags="-iframework /System/Library/PrivateFrameworks")
|
|
||||||
foreign import symbolication "system:CoreSymbolication.framework"
|
|
||||||
|
|
||||||
@(private="package")
|
|
||||||
_Trace_Entry :: rawptr
|
|
||||||
|
|
||||||
@(private="package")
|
|
||||||
_trace :: #force_no_inline proc(buf: Trace) -> (n: int) {
|
|
||||||
ctx: unw_context_t
|
|
||||||
cursor: unw_cursor_t
|
|
||||||
|
|
||||||
ret: i32
|
|
||||||
ret = unw_getcontext(&ctx)
|
|
||||||
assert(ret == 0)
|
|
||||||
ret = unw_init_local(&cursor, &ctx)
|
|
||||||
assert(ret == 0)
|
|
||||||
|
|
||||||
// Skip this function's frame and the caller.
|
|
||||||
if unw_step(&cursor) <= 0 { return }
|
|
||||||
|
|
||||||
pc: uintptr
|
|
||||||
for ; unw_step(&cursor) > 0 && n < len(buf); n += 1 {
|
|
||||||
ret = unw_get_reg(&cursor, .IP, &pc)
|
|
||||||
assert(ret == 0)
|
|
||||||
buf[n] = rawptr(pc)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private="package")
|
|
||||||
_lines_destroy :: proc(lines: []Line, allocator: runtime.Allocator) {
|
|
||||||
for line in lines {
|
|
||||||
delete(line.location, allocator)
|
|
||||||
delete(line.symbol, allocator)
|
|
||||||
}
|
|
||||||
delete(lines, allocator)
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private="package")
|
|
||||||
_lines :: proc(bt: Trace, allocator, _: runtime.Allocator) -> (out: []Line, err: Lines_Error) {
|
|
||||||
out = make([]Line, len(bt), allocator)
|
|
||||||
defer if err != nil { _lines_destroy(out, allocator) }
|
|
||||||
|
|
||||||
symbolicator := CSSymbolicatorCreateWithPid(posix.getpid())
|
|
||||||
defer CSRelease(symbolicator)
|
|
||||||
|
|
||||||
for &msg, i in out {
|
|
||||||
symbol := CSSymbolicatorGetSymbolWithAddressAtTime(symbolicator, uintptr(bt[i]), CSNow)
|
|
||||||
info := CSSymbolicatorGetSourceInfoWithAddressAtTime(symbolicator, uintptr(bt[i]), CSNow)
|
|
||||||
|
|
||||||
msg.symbol = strings.clone_from(CSSymbolGetName(symbol), allocator)
|
|
||||||
|
|
||||||
// No debug info.
|
|
||||||
if CSIsNull(info) {
|
|
||||||
owner := CSSymbolGetSymbolOwner(symbol)
|
|
||||||
msg.location = strings.clone_from(CSSymbolOwnerGetPath(owner), allocator)
|
|
||||||
} else {
|
|
||||||
path := string(CSSourceInfoGetPath(info))
|
|
||||||
location := strings.builder_make(allocator)
|
|
||||||
strings.write_string(&location, path)
|
|
||||||
when ODIN_ERROR_POS_STYLE == .Default {
|
|
||||||
strings.write_byte(&location, '(')
|
|
||||||
strings.write_int (&location, int(CSSourceInfoGetLineNumber(info)))
|
|
||||||
strings.write_byte(&location, ')')
|
|
||||||
} else when ODIN_ERROR_POS_STYLE == .Unix {
|
|
||||||
strings.write_byte(&location, ':')
|
|
||||||
strings.write_int (&location, int(CSSourceInfoGetLineNumber(info)))
|
|
||||||
} else {
|
|
||||||
#panic("unhandled ODIN_ERROR_POS_STYLE")
|
|
||||||
}
|
|
||||||
msg.location = strings.to_string(location)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
CSTypeRef :: struct {
|
|
||||||
csCppData: rawptr,
|
|
||||||
csCppObj: rawptr,
|
|
||||||
}
|
|
||||||
|
|
||||||
CSSymbolicatorRef :: distinct CSTypeRef
|
|
||||||
CSSymbolRef :: distinct CSTypeRef
|
|
||||||
CSSourceInfoRef :: distinct CSTypeRef
|
|
||||||
CSSymbolOwnerRef :: distinct CSTypeRef
|
|
||||||
|
|
||||||
CSNow :: 0x80000000
|
|
||||||
|
|
||||||
foreign symbolication {
|
|
||||||
@(link_name="CSIsNull")
|
|
||||||
_CSIsNull :: proc(ref: CSTypeRef) -> bool ---
|
|
||||||
@(link_name="CSRelease")
|
|
||||||
_CSRelease :: proc(ref: CSTypeRef) ---
|
|
||||||
|
|
||||||
CSSymbolicatorCreateWithPid :: proc(pid: posix.pid_t) -> CSSymbolicatorRef ---
|
|
||||||
|
|
||||||
CSSymbolicatorGetSymbolWithAddressAtTime :: proc(symbolicator: CSSymbolicatorRef, addr: uintptr, time: u64) -> CSSymbolRef ---
|
|
||||||
CSSymbolicatorGetSourceInfoWithAddressAtTime :: proc(symbolicator: CSSymbolicatorRef, adrr: uintptr, time: u64) -> CSSourceInfoRef ---
|
|
||||||
|
|
||||||
CSSymbolGetName :: proc(symbol: CSSymbolRef) -> cstring ---
|
|
||||||
CSSymbolGetSymbolOwner :: proc(symbol: CSSymbolRef) -> CSSymbolOwnerRef ---
|
|
||||||
|
|
||||||
CSSourceInfoGetPath :: proc(info: CSSourceInfoRef) -> cstring ---
|
|
||||||
CSSourceInfoGetLineNumber :: proc(info: CSSourceInfoRef) -> i32 ---
|
|
||||||
CSSourceInfoGetSymbol :: proc(info: CSSourceInfoRef) -> CSSymbolRef ---
|
|
||||||
|
|
||||||
CSSymbolOwnerGetPath :: proc(owner: CSSymbolOwnerRef) -> cstring ---
|
|
||||||
}
|
|
||||||
|
|
||||||
CSRelease :: #force_inline proc(ref: $T) {
|
|
||||||
_CSRelease(CSTypeRef(ref))
|
|
||||||
}
|
|
||||||
|
|
||||||
CSIsNull :: #force_inline proc(ref: $T) -> bool {
|
|
||||||
return _CSIsNull(CSTypeRef(ref))
|
|
||||||
}
|
|
||||||
|
|
||||||
// These could actually be smaller, but then we would have to define and check the size on each
|
|
||||||
// architecture, the sizes here are the largest they can be.
|
|
||||||
_LIBUNWIND_CONTEXT_SIZE :: 167
|
|
||||||
_LIBUNWIND_CURSOR_SIZE :: 204
|
|
||||||
|
|
||||||
unw_context_t :: struct {
|
|
||||||
data: [_LIBUNWIND_CONTEXT_SIZE]u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
unw_cursor_t :: struct {
|
|
||||||
data: [_LIBUNWIND_CURSOR_SIZE]u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cross-platform registers, each architecture has additional registers but these are enough for us.
|
|
||||||
Register :: enum i32 {
|
|
||||||
SP = -2,
|
|
||||||
IP = -1,
|
|
||||||
}
|
|
||||||
|
|
||||||
foreign system {
|
|
||||||
unw_getcontext :: proc(ctx: ^unw_context_t) -> i32 ---
|
|
||||||
unw_init_local :: proc(cursor: ^unw_cursor_t, ctx: ^unw_context_t) -> i32 ---
|
|
||||||
unw_get_reg :: proc(cursor: ^unw_cursor_t, name: Register, reg: ^uintptr) -> i32 ---
|
|
||||||
unw_step :: proc(cursor: ^unw_cursor_t) -> i32 ---
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,169 +0,0 @@
|
||||||
#+vet explicit-allocators
|
|
||||||
#+private file
|
|
||||||
package back
|
|
||||||
|
|
||||||
@require import "base:runtime"
|
|
||||||
@require import "base:intrinsics"
|
|
||||||
|
|
||||||
@require import "core:c"
|
|
||||||
@require import "core:c/libc"
|
|
||||||
@require import "core:os"
|
|
||||||
@require import "core:strings"
|
|
||||||
|
|
||||||
when !USE_FALLBACK {
|
|
||||||
|
|
||||||
foreign import lib "system:c"
|
|
||||||
|
|
||||||
@(private="package")
|
|
||||||
_Trace_Entry :: rawptr
|
|
||||||
|
|
||||||
@(private="package")
|
|
||||||
_trace :: #force_no_inline proc(buf: Trace) -> (n: int) {
|
|
||||||
// In order to omit this function's frame and the caller, we alloca a temp buffer with 2 extra slots.
|
|
||||||
bigger_buf := ([^]Trace_Entry)(intrinsics.alloca((2 + len(buf)) * size_of(Trace_Entry), align_of(Trace_Entry)))[:len(buf)+2]
|
|
||||||
_n := int(backtrace(raw_data(bigger_buf), i32(len(bigger_buf))))
|
|
||||||
if _n > 2 {
|
|
||||||
copy(buf, bigger_buf[2:])
|
|
||||||
return _n-2
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private="package")
|
|
||||||
_lines_destroy :: proc(msgs: []Line, allocator: runtime.Allocator) {
|
|
||||||
for msg in msgs {
|
|
||||||
delete(msg.location, allocator)
|
|
||||||
if msg.symbol != "??OOM" && msg.symbol != "??" { delete(msg.symbol, allocator) }
|
|
||||||
}
|
|
||||||
delete(msgs, allocator)
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private="package")
|
|
||||||
_lines :: proc(bt: Trace, allocator, temp_allocator: runtime.Allocator) -> (out: []Line, err: Lines_Error) {
|
|
||||||
msgs := backtrace_symbols(raw_data(bt), i32(len(bt)))[:len(bt)]
|
|
||||||
defer libc.free(raw_data(msgs))
|
|
||||||
|
|
||||||
out = make([]Line, len(bt), allocator)
|
|
||||||
defer if err != nil { _lines_destroy(out, allocator) }
|
|
||||||
|
|
||||||
// Debug info is needed.
|
|
||||||
when !ODIN_DEBUG {
|
|
||||||
for msg, i in msgs {
|
|
||||||
location, mem_err := strings.clone_from(msg, allocator)
|
|
||||||
if mem_err != nil { return out, .Out_Of_Memory }
|
|
||||||
|
|
||||||
out[i] = Line {
|
|
||||||
location = location,
|
|
||||||
symbol = "??",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
i := 0
|
|
||||||
|
|
||||||
command := make([dynamic]string, temp_allocator)
|
|
||||||
defer delete(command)
|
|
||||||
|
|
||||||
if _, err := append(&command, ADDR2LINE_PATH, "--functions", "--exe", ""); err != nil { return out, .Out_Of_Memory }
|
|
||||||
|
|
||||||
COMMAND_EXE_POS :: 3
|
|
||||||
COMMAND_START_LEN :: 4
|
|
||||||
|
|
||||||
for msg in msgs {
|
|
||||||
exe, addr := parse_address(msg) or_return
|
|
||||||
if command[COMMAND_EXE_POS] == "" {
|
|
||||||
command[COMMAND_EXE_POS] = exe
|
|
||||||
} else if command[COMMAND_EXE_POS] != exe {
|
|
||||||
i += exec_and_fill(command[:], out[i:], msgs[i:], allocator, temp_allocator) or_return
|
|
||||||
|
|
||||||
command[COMMAND_EXE_POS] = exe
|
|
||||||
resize(&command, COMMAND_START_LEN)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := append(&command, addr); err != nil { return out, .Out_Of_Memory }
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(command) > COMMAND_START_LEN {
|
|
||||||
i += exec_and_fill(command[:], out[i:], msgs[i:], allocator, temp_allocator) or_return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
// Parses the exe and address out of a backtrace line.
|
|
||||||
// Example: .../main(+0x20) [0x100000] -> .../main, +0x20, nil
|
|
||||||
parse_address :: proc(cmsg: cstring) -> (string, string, Lines_Error) {
|
|
||||||
msg := string(cmsg)
|
|
||||||
close_idx := strings.last_index_byte(msg, ')')
|
|
||||||
if close_idx < 1 {
|
|
||||||
return "", "", .Parse_Address_Fail
|
|
||||||
}
|
|
||||||
|
|
||||||
open_idx := strings.last_index_byte(msg[:close_idx], '(')
|
|
||||||
if open_idx < 0 {
|
|
||||||
return "", "", .Parse_Address_Fail
|
|
||||||
}
|
|
||||||
|
|
||||||
return msg[:open_idx], msg[open_idx+1:close_idx], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
exec_and_fill :: proc(command: []string, out: []Line, msgs: []cstring, allocator, temp_allocator: runtime.Allocator) -> (filled: int, err: Lines_Error) {
|
|
||||||
state, stdout, stderr, perr := os.process_exec({command = command}, temp_allocator)
|
|
||||||
defer delete(stdout, temp_allocator)
|
|
||||||
defer delete(stderr, temp_allocator)
|
|
||||||
|
|
||||||
if perr != nil || !state.success {
|
|
||||||
return 0, .Addr2line_Process_Error
|
|
||||||
}
|
|
||||||
|
|
||||||
count := len(command)-COMMAND_START_LEN
|
|
||||||
|
|
||||||
sstdout := string(stdout)
|
|
||||||
for i in 0..<count {
|
|
||||||
out[i].symbol, err = process_line(strings.split_lines_iterator(&sstdout), allocator)
|
|
||||||
if err == .Out_Of_Memory {
|
|
||||||
out[i].symbol = "??OOM"
|
|
||||||
} else if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: parse location and transform to ODIN_ERROR_POS_STYLE
|
|
||||||
out[i].location, err = process_line(strings.split_lines_iterator(&sstdout), allocator)
|
|
||||||
if err == .Out_Of_Memory {
|
|
||||||
out[i].location = "??OOM"
|
|
||||||
} else if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if out[i].location == "" || out[i].location == "??" {
|
|
||||||
fallback, mem_err := strings.clone_from(msgs[i], allocator)
|
|
||||||
if mem_err != nil { return 0, .Out_Of_Memory }
|
|
||||||
out[i].location = fallback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return count, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
process_line :: proc(line: string, ok: bool, allocator: runtime.Allocator) -> (string, Lines_Error) {
|
|
||||||
if !ok { return "", .Addr2line_Unexpected_EOF }
|
|
||||||
if line == "" { return "", .Addr2line_Output_Error }
|
|
||||||
|
|
||||||
if line == "??" {
|
|
||||||
return "??", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ret, err := strings.clone(strings.trim_right_space(line), allocator)
|
|
||||||
return ret, err == nil ? nil : .Out_Of_Memory
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
foreign lib {
|
|
||||||
backtrace :: proc(buffer: [^]rawptr, size: c.int) -> c.int ---
|
|
||||||
backtrace_symbols :: proc(buffer: [^]rawptr, size: c.int) -> [^]cstring ---
|
|
||||||
backtrace_symbols_fd :: proc(buffer: [^]rawptr, size: c.int, fd: ^libc.FILE) ---
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
#+vet explicit-allocators
|
|
||||||
package back
|
|
||||||
|
|
||||||
@require import "base:runtime"
|
|
||||||
|
|
||||||
@require import "core:strings"
|
|
||||||
|
|
||||||
when USE_FALLBACK {
|
|
||||||
|
|
||||||
when ODIN_OPTIMIZATION_MODE == .None {
|
|
||||||
#panic("the `back` package's `other` mode requires at least `-o:minimal` to work (it requires `#force_inline` to actually be applied)")
|
|
||||||
}
|
|
||||||
|
|
||||||
when ODIN_USE_SEPARATE_MODULES {
|
|
||||||
#panic("the `back` package's `other` mode requires `-use-single-module` to work (there are subtle instrumentation bugs to hunt down)")
|
|
||||||
}
|
|
||||||
|
|
||||||
@(no_instrumentation)
|
|
||||||
other_instrumentation_enter :: #force_inline proc "contextless" (a, b: rawptr, loc: runtime.Source_Code_Location) {
|
|
||||||
_other_instrumentation_enter(a, b, loc)
|
|
||||||
}
|
|
||||||
|
|
||||||
@(no_instrumentation)
|
|
||||||
other_instrumentation_exit :: #force_inline proc "contextless" (a, b: rawptr, loc: runtime.Source_Code_Location) {
|
|
||||||
_other_instrumentation_exit(a, b, loc)
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private="package")
|
|
||||||
_Trace_Entry :: runtime.Source_Code_Location
|
|
||||||
|
|
||||||
@(private="package")
|
|
||||||
_trace :: #force_no_inline proc(buf: Trace) -> (n: int) {
|
|
||||||
lframe := frame
|
|
||||||
|
|
||||||
// Omit this function's frame and the caller.
|
|
||||||
if lframe != nil { lframe = lframe.prev }
|
|
||||||
if lframe != nil { lframe = lframe.prev }
|
|
||||||
|
|
||||||
for lframe != nil && n < len(buf) {
|
|
||||||
buf[n] = lframe.loc
|
|
||||||
|
|
||||||
n += 1
|
|
||||||
lframe = lframe.prev
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private="package")
|
|
||||||
_lines_destroy :: proc(lines: []Line, allocator: runtime.Allocator) {
|
|
||||||
for line in lines {
|
|
||||||
delete(line.location, allocator)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private="package")
|
|
||||||
_lines :: proc(bt: Trace, allocator, temp_allocator: runtime.Allocator) -> (out: []Line, err: Lines_Error) {
|
|
||||||
out = make([]Line, len(bt), allocator)
|
|
||||||
|
|
||||||
for t, i in bt {
|
|
||||||
out[i].symbol = t.procedure
|
|
||||||
|
|
||||||
location := strings.builder_make(allocator)
|
|
||||||
strings.write_string(&location, t.file_path)
|
|
||||||
when ODIN_ERROR_POS_STYLE == .Default {
|
|
||||||
strings.write_byte(&location, '(')
|
|
||||||
strings.write_int (&location, int(t.line))
|
|
||||||
if t.column != 0 {
|
|
||||||
strings.write_byte(&location, ':')
|
|
||||||
strings.write_int (&location, int(t.column))
|
|
||||||
}
|
|
||||||
strings.write_byte(&location, ')')
|
|
||||||
} else when ODIN_ERROR_POS_STYLE == .Unix {
|
|
||||||
strings.write_byte(&location, ':')
|
|
||||||
strings.write_int (&location, int(t.line))
|
|
||||||
if t.column != 0 {
|
|
||||||
strings.write_byte(&location, ':')
|
|
||||||
strings.write_int (&location, int(t.column))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
#panic("unhandled ODIN_ERROR_POS_STYLE")
|
|
||||||
}
|
|
||||||
|
|
||||||
out[i].location = strings.to_string(location)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private="file")
|
|
||||||
Frame :: struct {
|
|
||||||
prev: ^Frame,
|
|
||||||
loc: runtime.Source_Code_Location,
|
|
||||||
}
|
|
||||||
|
|
||||||
@(thread_local, private="file")
|
|
||||||
frame: ^Frame
|
|
||||||
|
|
||||||
when OTHER_CUSTOM_INSTRUMENTATION {
|
|
||||||
@(no_instrumentation, private="file")
|
|
||||||
_other_instrumentation_enter :: #force_inline proc "contextless" (_, _: rawptr, loc: runtime.Source_Code_Location) {
|
|
||||||
frame = &Frame{
|
|
||||||
prev = frame,
|
|
||||||
loc = loc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@(no_instrumentation, private="file")
|
|
||||||
_other_instrumentation_exit :: #force_inline proc "contextless" (_, _: rawptr, loc: runtime.Source_Code_Location) {
|
|
||||||
frame = frame.prev
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
@(instrumentation_enter, private="file")
|
|
||||||
_other_instrumentation_enter :: #force_inline proc "contextless" (_, _: rawptr, loc: runtime.Source_Code_Location) {
|
|
||||||
frame = &Frame{
|
|
||||||
prev = frame,
|
|
||||||
loc = loc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@(instrumentation_exit, private="file")
|
|
||||||
_other_instrumentation_exit :: #force_inline proc "contextless" (_, _: rawptr, loc: runtime.Source_Code_Location) {
|
|
||||||
frame = frame.prev
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,140 +0,0 @@
|
||||||
#+vet explicit-allocators
|
|
||||||
#+private
|
|
||||||
package back
|
|
||||||
|
|
||||||
@require import "base:intrinsics"
|
|
||||||
@require import "base:runtime"
|
|
||||||
|
|
||||||
@require import "core:io"
|
|
||||||
@require import "core:strings"
|
|
||||||
@require import "core:sync"
|
|
||||||
@require import "core:unicode/utf16"
|
|
||||||
@require import win "core:sys/windows"
|
|
||||||
|
|
||||||
when !USE_FALLBACK {
|
|
||||||
|
|
||||||
SYMOPT_DEFERRED_LOADS :: 0x00000004
|
|
||||||
|
|
||||||
_Trace_Entry :: uintptr
|
|
||||||
|
|
||||||
_trace :: #force_no_inline proc(buf: Trace) -> (n: int) {
|
|
||||||
frame_count := win.RtlCaptureStackBackTrace(2, u32(len(buf)), ([^]rawptr)(raw_data(buf)), nil)
|
|
||||||
|
|
||||||
for &frame in buf[:frame_count] {
|
|
||||||
// NOTE: Return address is one after the call instruction so subtract a byte to
|
|
||||||
// end up back inside the call instruction which is needed for SymFromAddr.
|
|
||||||
frame -= 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return int(frame_count)
|
|
||||||
}
|
|
||||||
|
|
||||||
_lines_destroy :: proc(lines: []Line, allocator: runtime.Allocator) {
|
|
||||||
for line in lines {
|
|
||||||
delete(line.location, allocator)
|
|
||||||
if line.symbol != "??" && line.symbol != "??OOM" {
|
|
||||||
delete(line.symbol, allocator)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete(lines, allocator)
|
|
||||||
}
|
|
||||||
|
|
||||||
_lines :: proc(bt: Trace, allocator, temp_allocator: runtime.Allocator) -> (out: []Line, err: Lines_Error) {
|
|
||||||
// Debug info is needed, if we call with out-of-date debug symbols it will return out-of-date info, so better to short-circuit right away.
|
|
||||||
when !ODIN_DEBUG {
|
|
||||||
out = make([]Line, len(bt), allocator)
|
|
||||||
for &line, i in out {
|
|
||||||
line.symbol = "??"
|
|
||||||
|
|
||||||
location := strings.builder_make(allocator)
|
|
||||||
strings.write_string(&location, "0x")
|
|
||||||
strings.write_i64 (&location, i64(bt[i]), 16)
|
|
||||||
line.location = strings.to_string(location)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
out = make([]Line, len(bt), allocator)
|
|
||||||
defer if err != nil { _lines_destroy(out, allocator) }
|
|
||||||
|
|
||||||
process := win.GetCurrentProcess()
|
|
||||||
|
|
||||||
sync.guard(&_win32_dbghelp_mutex)
|
|
||||||
|
|
||||||
if !win.SymInitialize(process, nil, true) {
|
|
||||||
err = .Info_Not_Found
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer win.SymCleanup(process)
|
|
||||||
|
|
||||||
win.SymSetOptions(win.SYMOPT_LOAD_LINES|SYMOPT_DEFERRED_LOADS)
|
|
||||||
|
|
||||||
data: [size_of(win.SYMBOL_INFOW) + size_of([256]win.WCHAR)]byte
|
|
||||||
symbol := (^win.SYMBOL_INFOW)(&data[0])
|
|
||||||
// The value of SizeOfStruct must be the size of the whole struct,
|
|
||||||
// not just the size of the pointer
|
|
||||||
symbol.SizeOfStruct = size_of(symbol^)
|
|
||||||
symbol.MaxNameLen = 255
|
|
||||||
|
|
||||||
for &line, i in out {
|
|
||||||
if win.SymFromAddrW(process, win.DWORD64(bt[i]), nil, symbol) {
|
|
||||||
symbol, mem_err := win.wstring_to_utf8(cstring16(&symbol.Name[0]), int(symbol.NameLen), allocator)
|
|
||||||
if mem_err != nil {
|
|
||||||
line.symbol = "??OOM"
|
|
||||||
} else if symbol == "??" {
|
|
||||||
delete(symbol, allocator)
|
|
||||||
line.symbol = "??"
|
|
||||||
} else {
|
|
||||||
line.symbol = symbol
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
line.symbol = "??"
|
|
||||||
}
|
|
||||||
|
|
||||||
lineInfo: win.IMAGEHLP_LINE64
|
|
||||||
lineInfo.SizeOfStruct = size_of(lineInfo)
|
|
||||||
if win.SymGetLineFromAddrW64(process, win.DWORD64(bt[i]), &{}, &lineInfo) {
|
|
||||||
location := strings.builder_make(allocator)
|
|
||||||
write_string16(&location, string16(lineInfo.FileName))
|
|
||||||
when ODIN_ERROR_POS_STYLE == .Default {
|
|
||||||
strings.write_byte(&location, '(')
|
|
||||||
strings.write_int (&location, int(lineInfo.LineNumber))
|
|
||||||
strings.write_byte(&location, ')')
|
|
||||||
} else when ODIN_ERROR_POS_STYLE == .Unix {
|
|
||||||
strings.write_byte(&location, ':')
|
|
||||||
strings.write_int (&location, int(lineInfo.LineNumber))
|
|
||||||
} else {
|
|
||||||
#panic("unhandled ODIN_ERROR_POS_STYLE")
|
|
||||||
}
|
|
||||||
line.location = strings.to_string(location)
|
|
||||||
} else {
|
|
||||||
location := strings.builder_make(allocator)
|
|
||||||
strings.write_string(&location, "0x")
|
|
||||||
strings.write_i64 (&location, i64(bt[i]), 16)
|
|
||||||
line.location = strings.to_string(location)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
write_string16 :: proc(b: ^strings.Builder, s: string16, loc := #caller_location) -> (n: int, err: io.Error) {
|
|
||||||
for i := 0; i < len(s); i += 1 {
|
|
||||||
r := rune(utf16.REPLACEMENT_CHAR)
|
|
||||||
|
|
||||||
switch c := s[i]; {
|
|
||||||
case c < utf16._surr1, utf16._surr3 <= c:
|
|
||||||
r = rune(c)
|
|
||||||
case utf16._surr1 <= c && c < utf16._surr2 && i+1 < len(s) &&
|
|
||||||
utf16._surr2 <= s[i+1] && s[i+1] < utf16._surr3:
|
|
||||||
r = utf16.decode_surrogate_pair(rune(c), rune(s[i+1]))
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
n += strings.write_rune(b, rune(r)) or_return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
#+vet explicit-allocators
|
|
||||||
#+build !linux
|
|
||||||
#+build !darwin
|
|
||||||
#+build !netbsd
|
|
||||||
#+build !openbsd
|
|
||||||
#+build !freebsd
|
|
||||||
#+build !windows
|
|
||||||
package back
|
|
||||||
|
|
||||||
@(private="package")
|
|
||||||
_register_segfault_handler :: proc() {}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
#+vet explicit-allocators
|
|
||||||
#+build linux, darwin, netbsd, openbsd, freebsd
|
|
||||||
package back
|
|
||||||
|
|
||||||
import "base:runtime"
|
|
||||||
|
|
||||||
import "core:fmt"
|
|
||||||
import "core:mem"
|
|
||||||
import "core:sys/posix"
|
|
||||||
|
|
||||||
@(private="package")
|
|
||||||
_register_segfault_handler :: proc() {
|
|
||||||
posix.signal(.SIGSEGV, proc "c" (code: posix.Signal) {
|
|
||||||
context = runtime.default_context()
|
|
||||||
|
|
||||||
space: [16*mem.Kilobyte]byte
|
|
||||||
arena: mem.Arena
|
|
||||||
mem.arena_init(&arena, space[:])
|
|
||||||
allocator := mem.arena_allocator(&arena)
|
|
||||||
|
|
||||||
context.allocator = allocator
|
|
||||||
context.temp_allocator = allocator
|
|
||||||
|
|
||||||
backtrace: {
|
|
||||||
lines, err := lines(trace(), allocator, allocator)
|
|
||||||
if err != nil {
|
|
||||||
fmt.eprintf("Exception (Code: %i)\nCould not get backtrace: %v\n", code, err)
|
|
||||||
break backtrace
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.eprintf("Exception (Code: %i)\n[back trace]\n", code)
|
|
||||||
print(lines, temp_allocator=allocator)
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.exit(int(code))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
#+vet explicit-allocators
|
|
||||||
package back
|
|
||||||
|
|
||||||
import "base:runtime"
|
|
||||||
|
|
||||||
import "core:fmt"
|
|
||||||
import "core:mem"
|
|
||||||
import win "core:sys/windows"
|
|
||||||
|
|
||||||
_register_segfault_handler :: proc() {
|
|
||||||
win.SetUnhandledExceptionFilter(proc "stdcall" (exception_info: ^win.EXCEPTION_POINTERS) -> win.LONG {
|
|
||||||
context = runtime.default_context()
|
|
||||||
|
|
||||||
space: [16*mem.Kilobyte]byte
|
|
||||||
arena: mem.Arena
|
|
||||||
mem.arena_init(&arena, space[:])
|
|
||||||
allocator := mem.arena_allocator(&arena)
|
|
||||||
|
|
||||||
context.allocator = allocator
|
|
||||||
context.temp_allocator = allocator
|
|
||||||
|
|
||||||
fmt.eprint("Exception ")
|
|
||||||
if exception_info.ExceptionRecord != nil {
|
|
||||||
fmt.eprintf("(Type: %x, Flags: %x)\n", exception_info.ExceptionRecord.ExceptionCode, exception_info.ExceptionRecord.ExceptionFlags)
|
|
||||||
}
|
|
||||||
|
|
||||||
lines, err := lines(trace(), allocator, allocator)
|
|
||||||
if err != nil {
|
|
||||||
fmt.eprintln("Could not get backtrace: %v", err)
|
|
||||||
return win.EXCEPTION_CONTINUE_SEARCH
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.eprintln("[back trace]")
|
|
||||||
print(lines, temp_allocator=allocator)
|
|
||||||
|
|
||||||
return win.EXCEPTION_CONTINUE_SEARCH
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -2,18 +2,12 @@ package main
|
||||||
|
|
||||||
import rl "libraries/raylib"
|
import rl "libraries/raylib"
|
||||||
import "core:log"
|
import "core:log"
|
||||||
import "libraries/back"
|
|
||||||
|
|
||||||
main :: proc() {
|
main :: proc() {
|
||||||
// exe_path := os.args[0]
|
// exe_path := os.args[0]
|
||||||
// exe_dir := filepath.dir(string(exe_path))
|
// exe_dir := filepath.dir(string(exe_path))
|
||||||
// os.set_working_directory(exe_dir)
|
// os.set_working_directory(exe_dir)
|
||||||
rl.ChangeDirectory(rl.GetApplicationDirectory())
|
rl.ChangeDirectory(rl.GetApplicationDirectory())
|
||||||
back.register_segfault_handler()
|
|
||||||
context.assertion_failure_proc = back.assertion_failure_proc
|
|
||||||
register_illegal_instruction_handler()
|
|
||||||
|
|
||||||
|
|
||||||
context.logger = log.create_console_logger()
|
context.logger = log.create_console_logger()
|
||||||
|
|
||||||
init()
|
init()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
register_illegal_instruction_handler :: proc() {
|
|
||||||
_register_illegal_instruction_handler()
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
#+build linux
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "libraries/back"
|
|
||||||
import "core:sys/posix"
|
|
||||||
import "base:runtime"
|
|
||||||
import "core:mem"
|
|
||||||
import "core:fmt"
|
|
||||||
|
|
||||||
_register_illegal_instruction_handler :: proc() {
|
|
||||||
posix.signal(.SIGILL, proc "c" (code: posix.Signal) {
|
|
||||||
context = runtime.default_context()
|
|
||||||
|
|
||||||
space: [16*mem.Kilobyte]byte
|
|
||||||
arena: mem.Arena
|
|
||||||
mem.arena_init(&arena, space[:])
|
|
||||||
allocator := mem.arena_allocator(&arena)
|
|
||||||
|
|
||||||
context.allocator = allocator
|
|
||||||
context.temp_allocator = allocator
|
|
||||||
|
|
||||||
backtrace: {
|
|
||||||
lines, err := back.lines(back.trace(), allocator, allocator)
|
|
||||||
if err != nil {
|
|
||||||
fmt.eprintf("Exception (Code: %i)\nCould not get backtrace: %v\n", code, err)
|
|
||||||
break backtrace
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.eprintf("Exception (Code: %i)\n[back trace]\n", code)
|
|
||||||
back.print(lines, temp_allocator=allocator)
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.exit(int(code))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
#+build !linux
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
_register_illegal_instruction_handler :: proc() {}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue