|
4 | 4 | associated with that address, as well as the inline call stack leading |
5 | 5 | to that address. |
6 | 6 | *) |
| 7 | +open Durin |
| 8 | + |
| 9 | +(* Helper function to resolve dSYM paths similar to other examples *) |
| 10 | +let resolve_binary_path filename = |
| 11 | + if Sys.file_exists filename then (filename, false) |
| 12 | + else |
| 13 | + let dsym_path = |
| 14 | + filename ^ ".dSYM/Contents/Resources/DWARF/" ^ Filename.basename filename |
| 15 | + in |
| 16 | + if Sys.file_exists dsym_path then (dsym_path, true) else (filename, false) |
| 17 | + |
| 18 | +(* Initialize DWARF context from file *) |
| 19 | +let init_context filename = |
| 20 | + let actual_filename, _ = resolve_binary_path filename in |
| 21 | + if Sys.is_directory actual_filename then |
| 22 | + failwith (Printf.sprintf "'%s' is a directory" actual_filename) |
| 23 | + else |
| 24 | + let buffer = Object.Buffer.parse actual_filename in |
| 25 | + (buffer, actual_filename) |
| 26 | + |
| 27 | +(* Find line table entry for a given address using binary search *) |
| 28 | +let find_line_entry entries target_addr = |
| 29 | + let rec binary_search low high = |
| 30 | + if low > high then None |
| 31 | + else |
| 32 | + let mid = (low + high) / 2 in |
| 33 | + let entry = List.nth entries mid in |
| 34 | + let addr = entry.Dwarf.LineTable.address in |
| 35 | + if Unsigned.UInt64.equal addr target_addr then Some entry |
| 36 | + else if Unsigned.UInt64.compare target_addr addr < 0 then |
| 37 | + binary_search low (mid - 1) |
| 38 | + else |
| 39 | + (* Check if target is between this entry and the next *) |
| 40 | + if mid < List.length entries - 1 then |
| 41 | + let next_entry = List.nth entries (mid + 1) in |
| 42 | + let next_addr = next_entry.Dwarf.LineTable.address in |
| 43 | + if Unsigned.UInt64.compare target_addr next_addr < 0 then Some entry |
| 44 | + else binary_search (mid + 1) high |
| 45 | + else Some entry |
| 46 | + (* Last entry *) |
| 47 | + in |
| 48 | + if List.length entries = 0 then None |
| 49 | + else binary_search 0 (List.length entries - 1) |
| 50 | + |
| 51 | +(* Get section offset helper *) |
| 52 | +let get_section_offset buffer section_type = |
| 53 | + let object_format = Object_format.detect_format buffer in |
| 54 | + let section_name = |
| 55 | + Dwarf.object_format_to_section_name object_format section_type |
| 56 | + in |
| 57 | + try |
| 58 | + let open Macho in |
| 59 | + let _header, commands = read buffer in |
| 60 | + let sections = ref [] in |
| 61 | + List.iter |
| 62 | + (fun cmd -> |
| 63 | + match cmd with |
| 64 | + | LC_SEGMENT_64 (lazy segment) -> |
| 65 | + Array.iter |
| 66 | + (fun section -> |
| 67 | + if |
| 68 | + String.equal section.sec_segname "__DWARF" |
| 69 | + && String.equal section.sec_sectname section_name |
| 70 | + then |
| 71 | + sections := |
| 72 | + ( Unsigned.UInt32.to_int section.sec_offset, |
| 73 | + Unsigned.UInt64.to_int section.sec_size ) |
| 74 | + :: !sections) |
| 75 | + segment.seg_sections |
| 76 | + | LC_SEGMENT_32 (lazy segment) -> |
| 77 | + Array.iter |
| 78 | + (fun section -> |
| 79 | + if |
| 80 | + String.equal section.sec_segname "__DWARF" |
| 81 | + && String.equal section.sec_sectname section_name |
| 82 | + then |
| 83 | + sections := |
| 84 | + ( Unsigned.UInt32.to_int section.sec_offset, |
| 85 | + Unsigned.UInt64.to_int section.sec_size ) |
| 86 | + :: !sections) |
| 87 | + segment.seg_sections |
| 88 | + | _ -> ()) |
| 89 | + commands; |
| 90 | + match !sections with |
| 91 | + | (offset, size) :: _ -> |
| 92 | + Some (Unsigned.UInt64.of_int offset, Unsigned.UInt64.of_int size) |
| 93 | + | [] -> None |
| 94 | + with _ -> None |
| 95 | + |
| 96 | +(* Parse line table from debug_line section *) |
| 97 | +let parse_line_table buffer = |
| 98 | + match get_section_offset buffer Dwarf.Debug_line with |
| 99 | + | None -> None |
| 100 | + | Some (offset, _size) -> |
| 101 | + let cursor = |
| 102 | + Object.Buffer.cursor buffer ~at:(Unsigned.UInt64.to_int offset) |
| 103 | + in |
| 104 | + let header = Dwarf.LineTable.parse_line_program_header cursor buffer in |
| 105 | + let entries = Dwarf.LineTable.parse_line_program cursor header in |
| 106 | + Some (header, entries) |
| 107 | + |
| 108 | +(* Resolve address to source location *) |
| 109 | +let addr_to_location _buffer header entries addr = |
| 110 | + match find_line_entry entries addr with |
| 111 | + | None -> ("??", 0) |
| 112 | + | Some entry -> |
| 113 | + let file_index = Unsigned.UInt32.to_int entry.Dwarf.LineTable.file_index in |
| 114 | + if file_index < Array.length header.Dwarf.LineTable.file_names then |
| 115 | + let file_entry = header.Dwarf.LineTable.file_names.(file_index) in |
| 116 | + let filename = |
| 117 | + if file_entry.directory = "" then file_entry.name |
| 118 | + else file_entry.directory ^ "/" ^ file_entry.name |
| 119 | + in |
| 120 | + let line = Unsigned.UInt32.to_int entry.Dwarf.LineTable.line in |
| 121 | + (filename, line) |
| 122 | + else ("??", 0) |
| 123 | + |
| 124 | +(* Resolve DIE address attribute considering addr_base *) |
| 125 | +let resolve_die_address buffer addr_base addr_value = |
| 126 | + match addr_base with |
| 127 | + | Some base -> |
| 128 | + let index = Unsigned.UInt64.to_int addr_value in |
| 129 | + Dwarf.resolve_address_index buffer index base |
| 130 | + | None -> addr_value |
| 131 | + |
| 132 | +(* Find function name for address by searching debug_info DIEs *) |
| 133 | +let find_function_name buffer addr = |
| 134 | + try |
| 135 | + let dwarf = Dwarf.create buffer in |
| 136 | + let compile_units = Dwarf.parse_compile_units dwarf in |
| 137 | + let rec search_cu cu_seq = |
| 138 | + match cu_seq () with |
| 139 | + | Seq.Nil -> None |
| 140 | + | Seq.Cons (unit, rest) -> |
| 141 | + let header = Dwarf.CompileUnit.header unit in |
| 142 | + let abbrev_offset = |
| 143 | + Unsigned.UInt64.of_uint32 header.debug_abbrev_offset |
| 144 | + in |
| 145 | + let _, abbrev_table = Dwarf.get_abbrev_table dwarf abbrev_offset in |
| 146 | + (match Dwarf.CompileUnit.root_die unit abbrev_table buffer with |
| 147 | + | None -> search_cu rest |
| 148 | + | Some root_die -> |
| 149 | + (* Get addr_base from root DIE if present *) |
| 150 | + let addr_base = |
| 151 | + match Dwarf.DIE.find_attribute root_die Dwarf.DW_AT_addr_base with |
| 152 | + | Some (Dwarf.DIE.UData base) -> Some base |
| 153 | + | _ -> None |
| 154 | + in |
| 155 | + let rec search_die die = |
| 156 | + (* Check if this DIE is a subprogram containing the address *) |
| 157 | + (match die.Dwarf.DIE.tag with |
| 158 | + | Dwarf.DW_TAG_subprogram | Dwarf.DW_TAG_inlined_subroutine -> ( |
| 159 | + (* Get low_pc and high_pc *) |
| 160 | + let low_pc_opt = |
| 161 | + Dwarf.DIE.find_attribute die Dwarf.DW_AT_low_pc |
| 162 | + in |
| 163 | + let high_pc_opt = |
| 164 | + Dwarf.DIE.find_attribute die Dwarf.DW_AT_high_pc |
| 165 | + in |
| 166 | + match (low_pc_opt, high_pc_opt) with |
| 167 | + | Some (Dwarf.DIE.Address low_pc_raw), Some (Dwarf.DIE.Address high_pc_raw) -> |
| 168 | + (* Both are addresses - resolve them *) |
| 169 | + let low_pc = resolve_die_address buffer addr_base low_pc_raw in |
| 170 | + let high_pc = resolve_die_address buffer addr_base high_pc_raw in |
| 171 | + if |
| 172 | + Unsigned.UInt64.compare addr low_pc >= 0 |
| 173 | + && Unsigned.UInt64.compare addr high_pc < 0 |
| 174 | + then |
| 175 | + match Dwarf.DIE.find_attribute die Dwarf.DW_AT_name with |
| 176 | + | Some (Dwarf.DIE.String name) -> Some name |
| 177 | + | _ -> None |
| 178 | + else None |
| 179 | + | Some (Dwarf.DIE.Address low_pc_raw), Some (Dwarf.DIE.UData offset) -> |
| 180 | + (* high_pc is offset from low_pc *) |
| 181 | + let low_pc = resolve_die_address buffer addr_base low_pc_raw in |
| 182 | + let high_pc = Unsigned.UInt64.add low_pc offset in |
| 183 | + if |
| 184 | + Unsigned.UInt64.compare addr low_pc >= 0 |
| 185 | + && Unsigned.UInt64.compare addr high_pc < 0 |
| 186 | + then |
| 187 | + match Dwarf.DIE.find_attribute die Dwarf.DW_AT_name with |
| 188 | + | Some (Dwarf.DIE.String name) -> Some name |
| 189 | + | _ -> None |
| 190 | + else None |
| 191 | + | _ -> None) |
| 192 | + | _ -> None) |
| 193 | + |> function |
| 194 | + | Some name -> Some name |
| 195 | + | None -> |
| 196 | + (* Search children *) |
| 197 | + let rec search_children children_seq = |
| 198 | + match children_seq () with |
| 199 | + | Seq.Nil -> None |
| 200 | + | Seq.Cons (child, rest) -> ( |
| 201 | + match search_die child with |
| 202 | + | Some name -> Some name |
| 203 | + | None -> search_children rest) |
| 204 | + in |
| 205 | + search_children die.Dwarf.DIE.children |
| 206 | + in |
| 207 | + (match search_die root_die with |
| 208 | + | Some name -> Some name |
| 209 | + | None -> search_cu rest)) |
| 210 | + in |
| 211 | + search_cu compile_units |
| 212 | + with _ -> None |
| 213 | + |
| 214 | +(* Main addr2line lookup function *) |
| 215 | +let lookup_address buffer addr_str show_functions = |
| 216 | + try |
| 217 | + let addr = Unsigned.UInt64.of_string addr_str in |
| 218 | + match parse_line_table buffer with |
| 219 | + | None -> if show_functions then Printf.printf "??\n??:0\n" else Printf.printf "??:0\n" |
| 220 | + | Some (header, entries) -> |
| 221 | + let filename, line = addr_to_location buffer header entries addr in |
| 222 | + if show_functions then ( |
| 223 | + let func_name = |
| 224 | + match find_function_name buffer addr with |
| 225 | + | Some name -> name |
| 226 | + | None -> "??" |
| 227 | + in |
| 228 | + Printf.printf "%s\n%s:%d\n" func_name filename line) |
| 229 | + else Printf.printf "%s:%d\n" filename line |
| 230 | + with _ -> if show_functions then Printf.printf "??\n??:0\n" else Printf.printf "??:0\n" |
| 231 | + |
| 232 | +(* Command-line interface *) |
| 233 | +let executable_file = |
| 234 | + let doc = "Executable file to analyze" in |
| 235 | + Cmdliner.Arg.(value & opt (some string) None & info [ "e"; "exe" ] ~docv:"FILE" ~doc) |
| 236 | + |
| 237 | +let show_functions = |
| 238 | + let doc = "Show function names" in |
| 239 | + Cmdliner.Arg.(value & flag & info [ "f"; "functions" ] ~doc) |
| 240 | + |
| 241 | +let show_inlines = |
| 242 | + let doc = "Unwind inlined functions" in |
| 243 | + Cmdliner.Arg.(value & flag & info [ "i"; "inlines" ] ~doc) |
| 244 | + |
| 245 | +let pretty_print = |
| 246 | + let doc = "Make the output easier to read for humans" in |
| 247 | + Cmdliner.Arg.(value & flag & info [ "p"; "pretty-print" ] ~doc) |
| 248 | + |
| 249 | +let basenames = |
| 250 | + let doc = "Strip directory names" in |
| 251 | + Cmdliner.Arg.(value & flag & info [ "s"; "basenames" ] ~doc) |
| 252 | + |
| 253 | +let addresses = |
| 254 | + let doc = "Show addresses" in |
| 255 | + Cmdliner.Arg.(value & flag & info [ "a"; "addresses" ] ~doc) |
| 256 | + |
| 257 | +let demangle = |
| 258 | + let doc = "Demangle function names" in |
| 259 | + Cmdliner.Arg.(value & flag & info [ "C"; "demangle" ] ~doc) |
| 260 | + |
| 261 | +let addr_list = |
| 262 | + let doc = "Addresses to look up" in |
| 263 | + Cmdliner.Arg.(value & pos_all string [] & info [] ~docv:"ADDRESS" ~doc) |
| 264 | + |
| 265 | +let addr2line_cmd exec_file show_funcs _inlines _pretty _base _addrs _dem addrs = |
| 266 | + let filename = match exec_file with Some f -> f | None -> "a.out" in |
| 267 | + try |
| 268 | + let buffer, _ = init_context filename in |
| 269 | + if List.length addrs = 0 then |
| 270 | + (* Read from stdin *) |
| 271 | + try |
| 272 | + while true do |
| 273 | + let line = input_line stdin in |
| 274 | + let addr = String.trim line in |
| 275 | + if addr <> "" then lookup_address buffer addr show_funcs |
| 276 | + done |
| 277 | + with End_of_file -> () |
| 278 | + else |
| 279 | + (* Process command-line addresses *) |
| 280 | + List.iter (fun addr -> lookup_address buffer addr show_funcs) addrs |
| 281 | + with |
| 282 | + | Sys_error msg -> |
| 283 | + Printf.eprintf "Error: %s\n" msg; |
| 284 | + exit 1 |
| 285 | + | Failure msg -> |
| 286 | + Printf.eprintf "Error: %s\n" msg; |
| 287 | + exit 1 |
| 288 | + | exn -> |
| 289 | + Printf.eprintf "Error: %s\n" (Printexc.to_string exn); |
| 290 | + exit 1 |
| 291 | + |
| 292 | +let cmd = |
| 293 | + let doc = "Convert addresses to line number/file name pairs" in |
| 294 | + let info = Cmdliner.Cmd.info "addr2line" ~doc in |
| 295 | + Cmdliner.Cmd.v info |
| 296 | + Cmdliner.Term.( |
| 297 | + const addr2line_cmd $ executable_file $ show_functions $ show_inlines |
| 298 | + $ pretty_print $ basenames $ addresses $ demangle $ addr_list) |
| 299 | + |
| 300 | +let () = exit (Cmdliner.Cmd.eval cmd) |
0 commit comments