fuck submodules part 3
This commit is contained in:
parent
a1a94dec51
commit
88dee75f7d
26 changed files with 2962 additions and 0 deletions
19
libraries/snths_ui/example/back/LICENSE
Normal file
19
libraries/snths_ui/example/back/LICENSE
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
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.
|
||||
188
libraries/snths_ui/example/back/README.md
Normal file
188
libraries/snths_ui/example/back/README.md
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
# 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
|
||||
```
|
||||
230
libraries/snths_ui/example/back/allocator.odin
Normal file
230
libraries/snths_ui/example/back/allocator.odin
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
#+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)
|
||||
}
|
||||
}
|
||||
147
libraries/snths_ui/example/back/back.odin
Normal file
147
libraries/snths_ui/example/back/back.odin
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
#+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
|
||||
160
libraries/snths_ui/example/back/back_darwin.odin
Normal file
160
libraries/snths_ui/example/back/back_darwin.odin
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
#+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 ---
|
||||
}
|
||||
|
||||
}
|
||||
169
libraries/snths_ui/example/back/back_linux.odin
Normal file
169
libraries/snths_ui/example/back/back_linux.odin
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
#+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) ---
|
||||
}
|
||||
|
||||
}
|
||||
127
libraries/snths_ui/example/back/back_other.odin
Normal file
127
libraries/snths_ui/example/back/back_other.odin
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
#+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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
140
libraries/snths_ui/example/back/back_windows.odin
Normal file
140
libraries/snths_ui/example/back/back_windows.odin
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
#+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
|
||||
}
|
||||
|
||||
}
|
||||
11
libraries/snths_ui/example/back/signal_handler_other.odin
Normal file
11
libraries/snths_ui/example/back/signal_handler_other.odin
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#+vet explicit-allocators
|
||||
#+build !linux
|
||||
#+build !darwin
|
||||
#+build !netbsd
|
||||
#+build !openbsd
|
||||
#+build !freebsd
|
||||
#+build !windows
|
||||
package back
|
||||
|
||||
@(private="package")
|
||||
_register_segfault_handler :: proc() {}
|
||||
37
libraries/snths_ui/example/back/signal_handler_posix.odin
Normal file
37
libraries/snths_ui/example/back/signal_handler_posix.odin
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#+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))
|
||||
})
|
||||
}
|
||||
38
libraries/snths_ui/example/back/signal_handler_windows.odin
Normal file
38
libraries/snths_ui/example/back/signal_handler_windows.odin
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#+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
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue