trace_cpp.odin 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. #+private file
  2. #+build linux, darwin
  3. package debug_trace
  4. import "base:intrinsics"
  5. import "base:runtime"
  6. import "core:strings"
  7. import "core:fmt"
  8. import "core:c"
  9. // NOTE: Relies on C++23 which adds <stacktrace> and becomes ABI and that can be used
  10. foreign import stdcpplibbacktrace "system:stdc++_libbacktrace"
  11. foreign import libdl "system:dl"
  12. backtrace_state :: struct {}
  13. backtrace_error_callback :: proc "c" (data: rawptr, msg: cstring, errnum: c.int)
  14. backtrace_simple_callback :: proc "c" (data: rawptr, pc: uintptr) -> c.int
  15. backtrace_full_callback :: proc "c" (data: rawptr, pc: uintptr, filename: cstring, lineno: c.int, function: cstring) -> c.int
  16. backtrace_syminfo_callback :: proc "c" (data: rawptr, pc: uintptr, symname: cstring, symval: uintptr, symsize: uintptr)
  17. @(default_calling_convention="c", link_prefix="__glibcxx_")
  18. foreign stdcpplibbacktrace {
  19. backtrace_create_state :: proc(
  20. filename: cstring,
  21. threaded: c.int,
  22. error_callback: backtrace_error_callback,
  23. data: rawptr,
  24. ) -> ^backtrace_state ---
  25. backtrace_simple :: proc(
  26. state: ^backtrace_state,
  27. skip: c.int,
  28. callback: backtrace_simple_callback,
  29. error_callback: backtrace_error_callback,
  30. data: rawptr,
  31. ) -> c.int ---
  32. backtrace_pcinfo :: proc(
  33. state: ^backtrace_state,
  34. pc: uintptr,
  35. callback: backtrace_full_callback,
  36. error_callback: backtrace_error_callback,
  37. data: rawptr,
  38. ) -> c.int ---
  39. backtrace_syminfo :: proc(
  40. state: ^backtrace_state,
  41. addr: uintptr,
  42. callback: backtrace_syminfo_callback,
  43. error_callback: backtrace_error_callback,
  44. data: rawptr,
  45. ) -> c.int ---
  46. // NOTE(bill): this is technically an internal procedure, but it is exposed
  47. backtrace_free :: proc(
  48. state: ^backtrace_state,
  49. p: rawptr,
  50. size: c.size_t, // unused
  51. error_callback: backtrace_error_callback, // unused
  52. data: rawptr, // unused
  53. ) ---
  54. }
  55. Dl_info :: struct {
  56. dli_fname: cstring,
  57. dli_fbase: rawptr,
  58. dli_sname: cstring,
  59. dli_saddr: rawptr,
  60. }
  61. @(default_calling_convention="c")
  62. foreign libdl {
  63. dladdr :: proc(addr: rawptr, info: ^Dl_info) -> c.int ---
  64. }
  65. @(private="package")
  66. _Context :: struct {
  67. state: ^backtrace_state,
  68. }
  69. @(private="package")
  70. _init :: proc(ctx: ^Context) -> (ok: bool) {
  71. defer if !ok { destroy(ctx) }
  72. ctx.impl.state = backtrace_create_state("odin-debug-trace", 1, nil, ctx)
  73. return ctx.impl.state != nil
  74. }
  75. @(private="package")
  76. _destroy :: proc(ctx: ^Context) -> bool {
  77. if ctx != nil {
  78. backtrace_free(ctx.impl.state, nil, 0, nil, nil)
  79. }
  80. return true
  81. }
  82. @(private="package")
  83. _frames :: proc "contextless" (ctx: ^Context, skip: uint, frames_buffer: []Frame) -> (frames: []Frame) {
  84. Backtrace_Context :: struct {
  85. ctx: ^Context,
  86. frames: []Frame,
  87. frame_count: int,
  88. }
  89. btc := &Backtrace_Context{
  90. ctx = ctx,
  91. frames = frames_buffer,
  92. }
  93. backtrace_simple(
  94. ctx.impl.state,
  95. c.int(skip + 2),
  96. proc "c" (user: rawptr, address: uintptr) -> c.int {
  97. btc := (^Backtrace_Context)(user)
  98. address := Frame(address)
  99. if address == 0 {
  100. return 1
  101. }
  102. if btc.frame_count == len(btc.frames) {
  103. return 1
  104. }
  105. btc.frames[btc.frame_count] = address
  106. btc.frame_count += 1
  107. return 0
  108. },
  109. nil,
  110. btc,
  111. )
  112. if btc.frame_count > 0 {
  113. frames = btc.frames[:btc.frame_count]
  114. }
  115. return
  116. }
  117. @(private="package")
  118. _resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> Frame_Location {
  119. intrinsics.atomic_store(&ctx.in_resolve, true)
  120. defer intrinsics.atomic_store(&ctx.in_resolve, false)
  121. Backtrace_Context :: struct {
  122. rt_ctx: runtime.Context,
  123. allocator: runtime.Allocator,
  124. frame: Frame_Location,
  125. }
  126. btc := &Backtrace_Context{
  127. rt_ctx = context,
  128. allocator = allocator,
  129. }
  130. done := backtrace_pcinfo(
  131. ctx.impl.state,
  132. uintptr(frame),
  133. proc "c" (data: rawptr, address: uintptr, file: cstring, line: c.int, symbol: cstring) -> c.int {
  134. btc := (^Backtrace_Context)(data)
  135. context = btc.rt_ctx
  136. frame := &btc.frame
  137. if file != nil {
  138. frame.file_path = strings.clone_from_cstring(file, btc.allocator)
  139. } else if info: Dl_info; dladdr(rawptr(address), &info) != 0 && info.dli_fname != "" {
  140. frame.file_path = strings.clone_from_cstring(info.dli_fname, btc.allocator)
  141. }
  142. if symbol != nil {
  143. frame.procedure = strings.clone_from_cstring(symbol, btc.allocator)
  144. } else if info: Dl_info; dladdr(rawptr(address), &info) != 0 && info.dli_sname != "" {
  145. frame.procedure = strings.clone_from_cstring(info.dli_sname, btc.allocator)
  146. } else {
  147. frame.procedure = fmt.aprintf("(procedure: 0x%x)", allocator=btc.allocator)
  148. }
  149. frame.line = i32(line)
  150. return 0
  151. },
  152. nil,
  153. btc,
  154. )
  155. if done != 0 {
  156. return btc.frame
  157. }
  158. // NOTE(bill): pcinfo cannot resolve, but it might be possible to get the procedure name at least
  159. backtrace_syminfo(
  160. ctx.impl.state,
  161. uintptr(frame),
  162. proc "c" (data: rawptr, address: uintptr, symbol: cstring, _ignore0, _ignore1: uintptr) {
  163. if symbol != nil {
  164. btc := (^Backtrace_Context)(data)
  165. context = btc.rt_ctx
  166. btc.frame.procedure = strings.clone_from_cstring(symbol, btc.allocator)
  167. }
  168. },
  169. nil,
  170. btc,
  171. )
  172. return btc.frame
  173. }