├── .gitignore ├── README.md ├── build.sh ├── gui ├── main.go └── static │ ├── index.html │ ├── index.js │ └── style.css ├── main.c ├── media └── debug.gif └── tests ├── asm_test ├── .gitignore ├── build.sh └── main.s ├── build.sh ├── complex_structs ├── .gitignore ├── build.sh └── structs.c ├── file_collide ├── .gitignore ├── build.sh ├── collide │ └── file.c ├── common.h └── file.c ├── func_shadows ├── .gitignore ├── build.sh ├── def1.c ├── def2.c └── shadows.c ├── multiple_cu ├── .gitignore ├── build.sh ├── dummy.c └── extra.c ├── simple ├── .gitignore ├── build.sh └── simple.c ├── simple_funcs ├── .gitignore ├── build.sh └── simple.c └── var_shadows ├── .gitignore ├── build.sh └── shadows.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | debugger 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEBUG 2 | Building a small/simplicity focused linux debugger to learn DWARF 3 | 4 | ![Debug in Action](/media/debug.gif) 5 | 6 | DONE: 7 | - GUI with basic interaction support 8 | - Parse DWARF line and abbrev tables 9 | - Support breakpoints on lines and addresses 10 | - Support hardware watchpoints on variables 11 | 12 | TODO: 13 | - Handle simple user-defined C expressions 14 | - Easy printing memory 15 | - Support for sofware watchpoints 16 | - Load debug info for externally loaded dynamic libraries 17 | - Load and print callstack via .debug_frame 18 | - Overhaul internal line<>address table representation 19 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | while [[ $# -gt 0 ]]; do 2 | case $1 in 3 | -l|--local) 4 | # Shuts off network stack, runs local command processing only 5 | LOCAL="-D LOCAL" 6 | shift 7 | ;; 8 | *) 9 | shift 10 | ;; 11 | esac 12 | done 13 | 14 | clang -Os -g $LOCAL -Wall -Wextra -o debugger main.c 15 | 16 | cd tests 17 | ./build.sh 18 | -------------------------------------------------------------------------------- /gui/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "fmt" 6 | "log" 7 | "strings" 8 | "strconv" 9 | "sync" 10 | "io/ioutil" 11 | "net/http" 12 | "encoding/json" 13 | ) 14 | 15 | type File struct { 16 | Name string `json:"name"` 17 | Data string `json:"data"` 18 | } 19 | 20 | type Register struct { 21 | Name string `json:"name"` 22 | Value string `json:"value"` 23 | } 24 | 25 | type PositionInfo struct { 26 | Address string `json:"address"` 27 | Line string `json:"line"` 28 | File string `json:"file"` 29 | } 30 | 31 | type WatchInfo struct { 32 | Id string `json:"id"` 33 | Variable string `json:"variable"` 34 | Value string `json:"value"` 35 | } 36 | 37 | func run_command(dbg *DebugState, cmd string) (result string) { 38 | dbg.lock.Lock() 39 | _, err := dbg.conn.Write([]byte(cmd)) 40 | if err != nil { 41 | log.Fatal("Failed to send cmd!") 42 | } 43 | 44 | data := make([]byte, 4096) 45 | size, err := dbg.conn.Read(data) 46 | if err != nil { 47 | log.Fatal("Failed to get cmd response!") 48 | } 49 | dbg.lock.Unlock() 50 | 51 | str_data := string(data[0:size]) 52 | if len(str_data) < 5 { 53 | log.Fatal("Packet not large enough to be valid!\n") 54 | } 55 | 56 | if !strings.HasPrefix(str_data, "ok") { 57 | log.Fatal("Packet not happy\n") 58 | } 59 | 60 | str_data = str_data[3:] 61 | i := 0 62 | for ; i < len(str_data); i++ { 63 | if str_data[i] == ' ' { 64 | break 65 | } 66 | } 67 | if i == len(str_data) { 68 | log.Fatal("Packet length formatted weird\n") 69 | } 70 | 71 | num_str := str_data[0:i] 72 | data_len, err := strconv.Atoi(num_str) 73 | if err != nil { 74 | log.Fatal("Packet length formatted weird\n") 75 | } 76 | 77 | str_data = str_data[i+1:] 78 | if data_len != len(str_data) { 79 | log.Fatal("Packet length invalid?\n") 80 | } 81 | 82 | return str_data 83 | } 84 | 85 | func get_file(w http.ResponseWriter, r *http.Request) { 86 | w.Header().Set("Content-Type", "application/json") 87 | w.WriteHeader(http.StatusOK) 88 | 89 | files, ok := r.URL.Query()["file"] 90 | if !ok { 91 | log.Fatal("Invalid file!") 92 | } 93 | 94 | file_name := files[0] 95 | 96 | file_str, err := ioutil.ReadFile(file_name) 97 | if err != nil { 98 | fmt.Fprintf(w, "Failed to open file!") 99 | return 100 | } 101 | 102 | f := &File{Name: file_name, Data: string(file_str)} 103 | json.NewEncoder(w).Encode(f) 104 | } 105 | 106 | func get_file_list(w http.ResponseWriter, r *http.Request, dbg *DebugState) { 107 | w.Header().Set("Content-Type", "application/json") 108 | w.WriteHeader(http.StatusOK) 109 | 110 | str_data := run_command(dbg, "fs\n") 111 | 112 | type FilePath struct { 113 | Path string `json:"path"` 114 | Dir string `json:"dir"` 115 | Name string `json:"name"` 116 | } 117 | 118 | type Data struct { 119 | Paths []FilePath `json:"paths"` 120 | } 121 | 122 | path_lines := strings.Split(str_data, "\n") 123 | paths := make([]FilePath, len(path_lines) - 1) 124 | for i, path := range path_lines { 125 | if len(path) == 0 { 126 | break 127 | } 128 | 129 | pdata := strings.Split(path, " ") 130 | if len(pdata) != 3 { 131 | break 132 | } 133 | 134 | paths[i].Path = pdata[0] 135 | paths[i].Dir = pdata[1] 136 | paths[i].Name = pdata[2] 137 | } 138 | 139 | dt := &Data{Paths: paths} 140 | json.NewEncoder(w).Encode(dt) 141 | } 142 | 143 | func set_breakpoint(w http.ResponseWriter, r *http.Request, dbg *DebugState) { 144 | w.Header().Set("Content-Type", "application/json") 145 | w.WriteHeader(http.StatusOK) 146 | 147 | files, ok := r.URL.Query()["file"] 148 | if !ok { 149 | log.Fatal("Invalid file!") 150 | } 151 | 152 | lines, ok := r.URL.Query()["line"] 153 | if !ok { 154 | log.Fatal("Invalid line!") 155 | } 156 | 157 | line_num, err := strconv.Atoi(lines[0]) 158 | if err != nil { 159 | log.Fatal("Line is not a valid number!") 160 | } 161 | 162 | cmd := fmt.Sprintf("bl %d %s\n", line_num, files[0]) 163 | _ = run_command(dbg, cmd) 164 | } 165 | 166 | func set_watchpoint(w http.ResponseWriter, r *http.Request, dbg *DebugState) { 167 | w.Header().Set("Content-Type", "application/json") 168 | w.WriteHeader(http.StatusOK) 169 | 170 | vars, ok := r.URL.Query()["var"] 171 | if !ok { 172 | log.Fatal("Invalid variable!") 173 | } 174 | 175 | cmd := fmt.Sprintf("w %s\n", vars[0]) 176 | _ = run_command(dbg, cmd) 177 | } 178 | 179 | func clear_watchpoint(w http.ResponseWriter, r *http.Request, dbg *DebugState) { 180 | w.Header().Set("Content-Type", "application/json") 181 | w.WriteHeader(http.StatusOK) 182 | 183 | vars, ok := r.URL.Query()["var"] 184 | if !ok { 185 | log.Fatal("Invalid variable!") 186 | } 187 | 188 | cmd := fmt.Sprintf("dw %s\n", vars[0]) 189 | _ = run_command(dbg, cmd) 190 | } 191 | 192 | func clear_breakpoint(w http.ResponseWriter, r *http.Request, dbg *DebugState) { 193 | w.Header().Set("Content-Type", "application/json") 194 | w.WriteHeader(http.StatusOK) 195 | 196 | files, ok := r.URL.Query()["file"] 197 | if !ok { 198 | log.Fatal("Invalid file!") 199 | } 200 | 201 | lines, ok := r.URL.Query()["line"] 202 | if !ok { 203 | log.Fatal("Invalid line!") 204 | } 205 | 206 | line_num, err := strconv.Atoi(lines[0]) 207 | if err != nil { 208 | log.Fatal("Line is not a valid number!") 209 | } 210 | 211 | cmd := fmt.Sprintf("dl %d %s\n", line_num, files[0]) 212 | _ = run_command(dbg, cmd) 213 | } 214 | 215 | func get_registers(w http.ResponseWriter, r *http.Request, dbg *DebugState) { 216 | w.Header().Set("Content-Type", "application/json") 217 | w.WriteHeader(http.StatusOK) 218 | 219 | str_data := run_command(dbg, "p\n") 220 | 221 | type Data struct { 222 | Registers []Register `json:"registers"` 223 | } 224 | 225 | register_lines := strings.Split(str_data, "\n") 226 | regs := make([]Register, len(register_lines) - 1) 227 | 228 | for i, register := range register_lines { 229 | rdata := strings.Split(register, ": ") 230 | if len(rdata) == 1 { 231 | break 232 | } 233 | 234 | regs[i].Name = rdata[0] 235 | regs[i].Value = rdata[1] 236 | } 237 | 238 | json.NewEncoder(w).Encode(regs) 239 | } 240 | 241 | func get_watchpoints(w http.ResponseWriter, r *http.Request, dbg *DebugState) { 242 | w.Header().Set("Content-Type", "application/json") 243 | w.WriteHeader(http.StatusOK) 244 | 245 | str_data := run_command(dbg, "pw\n") 246 | fmt.Printf("watchpoints: [%s]\n", str_data) 247 | 248 | wp_lines := strings.Split(str_data, "\n") 249 | wps := make([]WatchInfo, len(wp_lines) - 1) 250 | 251 | for i, bp := range wp_lines { 252 | pos_chunks := strings.Split(bp, " ") 253 | if len(pos_chunks) == 0 || pos_chunks[0] == "" { 254 | break 255 | } 256 | 257 | if len(pos_chunks) == 3 { 258 | wps[i] = WatchInfo{Id: pos_chunks[0], Value: pos_chunks[1], Variable: pos_chunks[2]} 259 | } else { 260 | log.Fatal("why do I hate life?\n"); 261 | } 262 | } 263 | 264 | json.NewEncoder(w).Encode(wps) 265 | } 266 | 267 | func get_breakpoints(w http.ResponseWriter, r *http.Request, dbg *DebugState) { 268 | w.Header().Set("Content-Type", "application/json") 269 | w.WriteHeader(http.StatusOK) 270 | 271 | str_data := run_command(dbg, "pb\n") 272 | fmt.Printf("breakpoints: [%s]\n", str_data) 273 | 274 | bp_lines := strings.Split(str_data, "\n") 275 | bps := make([]PositionInfo, len(bp_lines) - 1) 276 | 277 | for i, bp := range bp_lines { 278 | pos_chunks := strings.Split(bp, " ") 279 | if len(pos_chunks) == 0 || pos_chunks[0] == "" { 280 | break 281 | } 282 | 283 | if len(pos_chunks) == 3 { 284 | bps[i] = PositionInfo{Address: pos_chunks[0], Line: pos_chunks[1], File: pos_chunks[2]} 285 | } else if len(pos_chunks) == 1 { 286 | bps[i] = PositionInfo{Address: pos_chunks[0], Line: "", File: ""} 287 | } else { 288 | log.Fatal("why do I hate life?\n"); 289 | } 290 | } 291 | 292 | json.NewEncoder(w).Encode(bps) 293 | } 294 | 295 | func get_current_position(w http.ResponseWriter, r *http.Request, dbg *DebugState) { 296 | w.Header().Set("Content-Type", "application/json") 297 | w.WriteHeader(http.StatusOK) 298 | 299 | str_data := run_command(dbg, "pc\n") 300 | 301 | pos_chunks := strings.Split(str_data, " ") 302 | address := pos_chunks[0] 303 | line := "" 304 | file := "" 305 | if len(pos_chunks) == 3 { 306 | line = pos_chunks[1] 307 | file = pos_chunks[2] 308 | file = file[:len(file)-1] // strip off trailing newline 309 | } 310 | 311 | pi := PositionInfo{Address: address, Line: line, File: file} 312 | json.NewEncoder(w).Encode(pi) 313 | } 314 | 315 | func step_into(w http.ResponseWriter, r *http.Request, dbg *DebugState) { 316 | w.Header().Set("Content-Type", "application/json") 317 | w.WriteHeader(http.StatusOK) 318 | 319 | _ = run_command(dbg, "si\n") 320 | } 321 | 322 | func single_step(w http.ResponseWriter, r *http.Request, dbg *DebugState) { 323 | w.Header().Set("Content-Type", "application/json") 324 | w.WriteHeader(http.StatusOK) 325 | 326 | _ = run_command(dbg, "sa\n") 327 | } 328 | 329 | func run_line(w http.ResponseWriter, r *http.Request, dbg *DebugState) { 330 | w.Header().Set("Content-Type", "application/json") 331 | w.WriteHeader(http.StatusOK) 332 | 333 | _ = run_command(dbg, "s\n") 334 | } 335 | 336 | func continue_program(w http.ResponseWriter, r *http.Request, dbg *DebugState) { 337 | w.Header().Set("Content-Type", "application/json") 338 | w.WriteHeader(http.StatusOK) 339 | 340 | _ = run_command(dbg, "c\n") 341 | } 342 | 343 | func restart(w http.ResponseWriter, r *http.Request, dbg *DebugState) { 344 | w.Header().Set("Content-Type", "application/json") 345 | w.WriteHeader(http.StatusOK) 346 | 347 | _ = run_command(dbg, "r\n") 348 | } 349 | 350 | type DebugState struct { 351 | conn net.Conn 352 | lock sync.Mutex 353 | } 354 | 355 | func main() { 356 | port_str := ":8675" 357 | 358 | conn, err := net.Dial("tcp", "127.0.0.1:5000") 359 | if err != nil { 360 | log.Fatal("Failed to connect to debugger!") 361 | } 362 | 363 | dbg := &DebugState{conn: conn} 364 | 365 | fs := http.FileServer(http.Dir("./static")) 366 | http.HandleFunc("/get_file", get_file) 367 | http.HandleFunc("/set_breakpoint", func(w http.ResponseWriter, req *http.Request) { set_breakpoint(w, req, dbg) }) 368 | http.HandleFunc("/set_watchpoint", func(w http.ResponseWriter, req *http.Request) { set_watchpoint(w, req, dbg) }) 369 | http.HandleFunc("/clear_breakpoint", func(w http.ResponseWriter, req *http.Request) { clear_breakpoint(w, req, dbg) }) 370 | http.HandleFunc("/clear_watchpoint", func(w http.ResponseWriter, req *http.Request) { clear_watchpoint(w, req, dbg) }) 371 | http.HandleFunc("/watchpoints", func(w http.ResponseWriter, req *http.Request) { get_watchpoints(w, req, dbg) }) 372 | http.HandleFunc("/breakpoints", func(w http.ResponseWriter, req *http.Request) { get_breakpoints(w, req, dbg) }) 373 | http.HandleFunc("/get_registers", func(w http.ResponseWriter, req *http.Request) { get_registers(w, req, dbg) }) 374 | http.HandleFunc("/single_step", func(w http.ResponseWriter, req *http.Request) { single_step(w, req, dbg) }) 375 | http.HandleFunc("/step_into", func(w http.ResponseWriter, req *http.Request) { step_into(w, req, dbg) }) 376 | http.HandleFunc("/run_line", func(w http.ResponseWriter, req *http.Request) { run_line(w, req, dbg) }) 377 | http.HandleFunc("/cont", func(w http.ResponseWriter, req *http.Request) { continue_program(w, req, dbg) }) 378 | http.HandleFunc("/get_file_list", func(w http.ResponseWriter, req *http.Request) { get_file_list(w, req, dbg) }) 379 | http.HandleFunc("/current_position", func(w http.ResponseWriter, req *http.Request) { get_current_position(w, req, dbg) }) 380 | http.HandleFunc("/restart", func(w http.ResponseWriter, req *http.Request) { restart(w, req, dbg) }) 381 | http.Handle("/", fs) 382 | 383 | log.Printf("Listening on %s...\n", port_str) 384 | err = http.ListenAndServe(port_str, nil) 385 | if err != nil { 386 | log.Fatal(err) 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /gui/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 |
17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 |
25 |
26 |
27 |
28 |
29 |

Memory

30 |

0x7ffee10101f44: 0x40 0x50 0x10 0x12 0x13 0x40 0x50 0x10 0x12 0x13

31 |

0x7ffee10101f48: 0x40 0x50 0x10 0x12 0x13 0x40 0x50 0x10 0x12 0x13

32 |

0x7ffee10101f4B: 0x40 0x50 0x10 0x12 0x13 0x40 0x50 0x10 0x12 0x13

33 |

0x7ffee10101f4f: 0x40 0x50 0x10 0x12 0x13 0x40 0x50 0x10 0x12 0x13

34 |
35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /gui/static/index.js: -------------------------------------------------------------------------------- 1 | let files = []; 2 | let breakpoints = []; 3 | let watchpoints = []; 4 | let registers = []; 5 | 6 | let should_render_file = true; 7 | let current_file = 0; 8 | let current_position = {address: "", line: "", file: ""}; 9 | let last_position = {address: "", line: "", file: ""}; 10 | 11 | function attach_frag_children(host_elem, frag_elem) { 12 | if (host_elem.firstChild) { 13 | let section_parent = host_elem.parentElement; 14 | let new_host_elem = host_elem.cloneNode(); 15 | new_host_elem.appendChild(frag_elem); 16 | section_parent.replaceChild(new_host_elem, host_elem) 17 | } else { 18 | host_elem.appendChild(frag_elem); 19 | } 20 | } 21 | 22 | function generate_tagged_text(tag_type, text) { 23 | let tag_elem = document.createElement(tag_type); 24 | let text_elem = document.createTextNode(text); 25 | tag_elem.appendChild(text_elem); 26 | 27 | return tag_elem; 28 | } 29 | 30 | async function toggle_breakpoint(file_name, line_num) { 31 | let line_elem = document.getElementById('code-line-' + line_num); 32 | let toggle_val = line_elem.classList.toggle("line-break"); 33 | 34 | if (toggle_val) { 35 | await fetch('/set_breakpoint?file=' + file_name + "&line=" + line_num); 36 | } else { 37 | await fetch('/clear_breakpoint?file=' + file_name + "&line=" + line_num); 38 | } 39 | 40 | await get_breakpoints(); 41 | } 42 | 43 | async function get_file_list() { 44 | let response = await fetch('/get_file_list'); 45 | let file_list = await response.json(); 46 | files = file_list.paths; 47 | current_file = 0; 48 | 49 | for (let i = 0; i < files.length; i++) { 50 | files[i].data = await get_file(files[i]); 51 | } 52 | } 53 | 54 | async function get_file(path_blob) { 55 | let file_path = path_blob.path + "/" + path_blob.dir + "/" + path_blob.name; 56 | let response = await fetch('/get_file?file=' + file_path); 57 | let file_blob = await response.json(); 58 | return file_blob; 59 | } 60 | 61 | async function single_step() { 62 | await fetch('/single_step'); 63 | await get_registers(); 64 | await get_current_position(); 65 | await get_breakpoints(); 66 | await get_watchpoints(); 67 | 68 | render_page(true); 69 | } 70 | 71 | async function step_into() { 72 | await fetch('/step_into'); 73 | await get_registers(); 74 | await get_current_position(); 75 | await get_breakpoints(); 76 | await get_watchpoints(); 77 | 78 | render_page(true); 79 | } 80 | 81 | async function run_line() { 82 | await fetch('/run_line'); 83 | await get_registers(); 84 | await get_current_position(); 85 | await get_breakpoints(); 86 | await get_watchpoints(); 87 | 88 | render_page(true); 89 | } 90 | 91 | async function cont() { 92 | await fetch('/cont'); 93 | await get_registers(); 94 | await get_current_position(); 95 | await get_breakpoints(); 96 | await get_watchpoints(); 97 | 98 | render_page(true); 99 | } 100 | 101 | async function restart() { 102 | await fetch('/restart'); 103 | current_position = {address: "", line: "", file: ""}; 104 | old_position = {address: "", line: "", file: ""}; 105 | 106 | await get_registers(); 107 | await get_current_position(); 108 | await get_breakpoints(); 109 | await get_watchpoints(); 110 | 111 | render_page(true); 112 | } 113 | 114 | async function get_registers() { 115 | let response = await fetch('/get_registers'); 116 | registers = await response.json(); 117 | } 118 | 119 | async function get_current_position() { 120 | let response = await fetch('/current_position'); 121 | let new_position = await response.json(); 122 | 123 | old_position = current_position; 124 | current_position = new_position; 125 | } 126 | 127 | async function get_breakpoints() { 128 | let response = await fetch('/breakpoints'); 129 | breakpoints = await response.json(); 130 | } 131 | 132 | async function get_watchpoints() { 133 | let response = await fetch('/watchpoints'); 134 | watchpoints = await response.json(); 135 | } 136 | 137 | function render_registers() { 138 | let reg_display_elem = document.getElementById('register-display'); 139 | let reg_frag = document.createDocumentFragment(); 140 | 141 | reg_frag.appendChild(generate_tagged_text("h3", "Registers")); 142 | 143 | for (let i = 0; i < registers.length; i++) { 144 | let register = registers[i]; 145 | 146 | let reg_line = document.createElement("div"); 147 | reg_line.classList.add("register-line"); 148 | 149 | reg_line.appendChild(generate_tagged_text("p", register.name + ":")); 150 | reg_line.appendChild(generate_tagged_text("span", register.value)); 151 | reg_frag.appendChild(reg_line); 152 | } 153 | 154 | attach_frag_children(reg_display_elem, reg_frag); 155 | } 156 | 157 | function render_file_list() { 158 | let ftree_display_elem = document.getElementById('ftree-display'); 159 | 160 | let ftree_frag = document.createDocumentFragment(); 161 | for (let i = 0; i < files.length; i++) { 162 | let file = files[i]; 163 | let fline = generate_tagged_text("p", file.name); 164 | 165 | if (i == current_file) { 166 | fline.classList.add("current-file"); 167 | } else { 168 | let cur_idx = i.valueOf(); 169 | fline.addEventListener("click", () => { 170 | current_file = cur_idx; 171 | should_render_file = true; 172 | render_page(false); 173 | }); 174 | } 175 | 176 | ftree_frag.appendChild(fline); 177 | } 178 | 179 | attach_frag_children(ftree_display_elem, ftree_frag); 180 | } 181 | 182 | function render_breakpoints() { 183 | for (let i = 0; i < breakpoints.length; i++) { 184 | let bp = breakpoints[i]; 185 | if (bp.file == files[current_file].name) { 186 | let line_elem = document.getElementById('code-line-' + bp.line); 187 | line_elem.classList.add("line-break"); 188 | } 189 | } 190 | } 191 | 192 | function render_position() { 193 | if (current_position.line == "") { 194 | return; 195 | } 196 | 197 | if (current_position.file == files[current_file].name) { 198 | let line_elem = document.getElementById('code-line-' + current_position.line); 199 | line_elem.classList.add("line-active"); 200 | } 201 | 202 | if (current_position.line != "" && current_position.line != old_position.line && old_position.file == files[current_file].name) { 203 | let old_line_elem = document.getElementById('code-line-' + old_position.line); 204 | old_line_elem.classList.remove("line-active"); 205 | } 206 | } 207 | 208 | function render_file() { 209 | let file = files[current_file]; 210 | 211 | let file_elem = document.getElementById('file-display'); 212 | 213 | if (!should_render_file) { 214 | return; 215 | } 216 | 217 | should_render_file = false; 218 | 219 | let chunk = file.data.data; 220 | let file_name = file.name; 221 | 222 | let re = /\n|\r|\r\n/gm; 223 | let start_idx = 0; 224 | let file_frag = document.createDocumentFragment(); 225 | let line_count = 1; 226 | 227 | file_frag.appendChild(generate_tagged_text("h3", "File - " + file_name)); 228 | 229 | let file_display_elem = document.createElement("div"); 230 | for (;;) { 231 | let line_elem = document.createElement("div"); 232 | line_elem.classList.add("code-line-wrapper"); 233 | 234 | let pre_elem = document.createElement("pre"); 235 | let code_elem = document.createElement("code"); 236 | pre_elem.appendChild(code_elem); 237 | 238 | let ret = re.exec(chunk); 239 | if (!ret) { 240 | break; 241 | } 242 | 243 | let rem = chunk.substring(0, re.lastIndex); 244 | let line = chunk.substring(0, re.lastIndex); 245 | chunk = chunk.substring(re.lastIndex); 246 | re.lastIndex = 0; 247 | 248 | let count_elem = document.createElement("span"); 249 | let cur_line_count = line_count.valueOf(); 250 | count_elem.setAttribute('data-line-number', cur_line_count); 251 | count_elem.classList.add('code-line'); 252 | count_elem.addEventListener("click", () => { toggle_breakpoint(file_name, cur_line_count); }); 253 | 254 | line_elem.setAttribute('id', 'code-line-' + cur_line_count); 255 | line_elem.appendChild(count_elem); 256 | 257 | if (line[0] === undefined) { 258 | let break_elem = document.createElement("br"); 259 | line_elem.appendChild(break_elem); 260 | } else { 261 | let file_text = document.createTextNode(line); 262 | code_elem.appendChild(file_text); 263 | line_elem.appendChild(pre_elem); 264 | } 265 | 266 | file_display_elem.appendChild(line_elem); 267 | start_idx = re.lastIndex; 268 | line_count += 1; 269 | } 270 | 271 | let line_count_width = Math.floor(Math.log10(line_count)) + 1; 272 | document.documentElement.style.setProperty('--line-count-width', line_count_width + 'ch'); 273 | 274 | file_frag.appendChild(file_display_elem); 275 | attach_frag_children(file_elem, file_frag); 276 | } 277 | 278 | function render_watchpoints() { 279 | let watchpoint_elem = document.getElementById('watchpoint-display'); 280 | let watch_frag = document.createDocumentFragment(); 281 | 282 | watch_frag.appendChild(generate_tagged_text("h3", "Watchpoints")); 283 | let input_elem = document.createElement("input"); 284 | input_elem.setAttribute('placeholder', 'enter watchpoint here...'); 285 | input_elem.addEventListener("keyup", (ev) => { 286 | if (ev.keyCode == 13) { 287 | event.preventDefault(); 288 | 289 | fetch('/set_watchpoint?var=' + ev.target.value) 290 | .then(get_watchpoints) 291 | .then(render_watchpoints); 292 | } 293 | }); 294 | watch_frag.appendChild(input_elem); 295 | 296 | for (let i = 0; i < watchpoints.length; i++) { 297 | let wp = watchpoints[i]; 298 | 299 | let watch_line = document.createElement("div"); 300 | watch_line.classList.add("watch-line"); 301 | 302 | watch_line.appendChild(generate_tagged_text("p", wp.variable + ":")); 303 | watch_line.appendChild(generate_tagged_text("span", wp.value)); 304 | 305 | watch_frag.appendChild(watch_line); 306 | } 307 | 308 | attach_frag_children(watchpoint_elem, watch_frag); 309 | } 310 | 311 | function render_page(cursor_changed) { 312 | render_registers(); 313 | 314 | if (cursor_changed) { 315 | for (let i = 0; i < files.length; i++) { 316 | if (files[i].name == current_position.file) { 317 | current_file = i; 318 | break; 319 | } 320 | } 321 | } 322 | 323 | render_file_list(); 324 | render_file(); 325 | render_breakpoints(); 326 | render_position(); 327 | render_watchpoints(); 328 | } 329 | 330 | async function main() { 331 | await get_file_list(); 332 | await get_registers(); 333 | await get_current_position(); 334 | await get_breakpoints(); 335 | await get_watchpoints(); 336 | 337 | render_page(true); 338 | } 339 | -------------------------------------------------------------------------------- /gui/static/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --line-count-width: 1ch; 3 | --bright-color: #18bc9c; 4 | --bright-color2: #8ff0dc; 5 | --bg-color: #AFAFAF; 6 | --bg-color2: #DFDFDF; 7 | } 8 | 9 | body { 10 | margin: 0; 11 | } 12 | 13 | p, h1, h2, h3, h4, h5, pre { 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | p { 19 | padding-bottom: 0.125rem; 20 | font-size: 1rem; 21 | } 22 | h1, h2, h3, h4, h5 { 23 | padding-bottom: 0.5rem; 24 | font-family: 'Fira Sans', sans-serif; 25 | } 26 | 27 | code { 28 | font-family: 'Fira Code', monospace; 29 | } 30 | 31 | nav { 32 | display: flex; 33 | flex-direction: row; 34 | align-items: center; 35 | 36 | background-color: var(--bright-color); 37 | color: white; 38 | height: 2.5em; 39 | padding-left: 1em; 40 | padding-right: 1em; 41 | } 42 | 43 | nav > h2 { 44 | padding: 0; 45 | } 46 | 47 | .current-file { 48 | background-color: var(--bright-color2); 49 | } 50 | 51 | .content { 52 | display: grid; 53 | grid-template-columns: 15em 1fr 15em; 54 | grid-template-rows: 3em 1fr 1fr 1fr 1fr; 55 | grid-gap: 10px; 56 | height: calc(100vh - 2.5em); 57 | } 58 | 59 | .content section:nth-child(1) { 60 | grid-column: 1; 61 | grid-row: span 3; 62 | } 63 | 64 | .content section:nth-child(2) { 65 | grid-column: span 3; 66 | grid-row: 1; 67 | } 68 | 69 | .content section:nth-child(3) { 70 | grid-column: 2; 71 | grid-row: span 3; 72 | } 73 | 74 | .content section:nth-child(4) { 75 | grid-column: 3; 76 | grid-row: span 2; 77 | } 78 | 79 | .content section:nth-child(5) { 80 | grid-column: 3; 81 | grid-row: 4; 82 | } 83 | 84 | .content section:nth-child(6) { 85 | grid-column-end: span 3; 86 | grid-row-end: span 1; 87 | } 88 | 89 | #file-display { 90 | overflow-y: auto; 91 | } 92 | 93 | #ftree-display { 94 | padding: 0; 95 | padding-top: 1em; 96 | padding-bottom: 1em; 97 | background-color: var(--bg-color); 98 | } 99 | 100 | #ftree-display > p { 101 | padding: 0.25em; 102 | } 103 | 104 | #ftree-display > p:not(.current-file) { 105 | cursor: pointer; 106 | } 107 | 108 | #watchpoint-display { 109 | background-color: var(--bg-color); 110 | } 111 | 112 | #register-display { 113 | overflow-y: auto; 114 | background-color: var(--bg-color); 115 | } 116 | 117 | #memory-display { 118 | background-color: var(--bg-color); 119 | } 120 | 121 | #controls { 122 | display: flex; 123 | flex-direction: row; 124 | align-items: center; 125 | background-color: var(--bg-color); 126 | 127 | padding-top: 0; 128 | padding-bottom: 0; 129 | } 130 | #controls > button { 131 | height: 4ch; 132 | width: 4ch; 133 | font-size: 1rem; 134 | margin-left: 0.25em; 135 | margin-right: 0.25em; 136 | 137 | background-color: var(--bg-color2); 138 | 139 | } 140 | #controls > button:nth-child(1) { 141 | margin-left: 0; 142 | } 143 | #controls > button:last-child { 144 | margin-right: 0; 145 | } 146 | 147 | 148 | .code-line-wrapper { 149 | display: flex; 150 | flex-direction: row; 151 | align-items: center; 152 | tab-size: 4; 153 | } 154 | 155 | .code-line-wrapper > span { 156 | min-width: var(--line-count-width); 157 | margin-right: 0.75em; 158 | font-family: 'Fira Code', monospace; 159 | text-align: right; 160 | color: grey; 161 | } 162 | 163 | .code-line::before { 164 | content: attr(data-line-number); 165 | } 166 | 167 | .code-line { 168 | cursor: pointer; 169 | } 170 | 171 | .line-break { 172 | background-color: var(--bright-color); 173 | color: white; 174 | } 175 | 176 | .line-active { 177 | background-color: var(--bright-color2) !important; 178 | color: black; 179 | } 180 | 181 | .register-line { 182 | display: flex; 183 | flex-direction: row; 184 | align-items: center; 185 | font-family: 'Fira Code', monospace; 186 | } 187 | .register-line > p { 188 | font-weight: bold; 189 | margin-right: 0.25rem; 190 | font-size: 1.125rem; 191 | width: 4ch; 192 | } 193 | 194 | .watch-line { 195 | display: flex; 196 | flex-direction: row; 197 | align-items: center; 198 | font-family: 'Fira Code', monospace; 199 | } 200 | .watch-line > p { 201 | font-weight: bold; 202 | margin-right: 0.25rem; 203 | font-size: 1.125rem; 204 | } 205 | 206 | section { 207 | background-color: var(--bg-color2); 208 | padding: 1em; 209 | font-family: monospace; 210 | box-shadow: 2px 2px 3px grey; 211 | } 212 | 213 | input { 214 | width: 100%; 215 | max-width: 100%; 216 | box-sizing: border-box; 217 | margin-bottom: 1em; 218 | } 219 | 220 | i { 221 | cursor: pointer; 222 | } 223 | 224 | i:hover { 225 | color: darkgrey; 226 | } 227 | 228 | i:active { 229 | color: darkgrey; 230 | } 231 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | /* 20 | * Handy References: 21 | * - https://refspecs.linuxbase.org/elf/elf.pdf 22 | * - http://man7.org/linux/man-pages/man5/elf.5.html 23 | * - http://dwarfstd.org/doc/DWARF4.pdf 24 | * - https://wiki.osdev.org/DWARF 25 | */ 26 | 27 | // MACROS 28 | #define panic(...) do { dprintf(2, __VA_ARGS__); exit(1); } while (0) 29 | #define assert(x, ...) do { if ((x)) { dprintf(2, __VA_ARGS__); exit(1); } } while (0) 30 | #define happy_death(...) do { dprintf(2, __VA_ARGS__); exit(0); } while (0) 31 | 32 | // GLOBALS 33 | #define HW_SLOTS_MAX 4 34 | #define BLOCK_STACK_MAX 20 35 | #define LINE_TABLES_MAX 20 36 | #define TYPE_CHAIN_MAX 20 37 | #define MEMBERS_MAX 30 38 | #define VAL_STACK_MAX 32 39 | #define BREAKPOINTS_MAX 1024 40 | #define WATCHPOINTS_MAX 1024 41 | #define LINE_BUFFER_MAX 1024 42 | #define BLOCK_MAX 1024 43 | #define ABBREV_MAX 1024 44 | #define OUTBUF_MAX 4096 45 | 46 | // DEFINES 47 | #define DT_NULL 0 48 | #define DT_NEEDED 1 49 | #define DT_STRTAB 5 50 | #define DT_SYMTAB 6 51 | 52 | enum { 53 | SHT_NULL = 0, 54 | SHT_PROGBITS, 55 | SHT_SYMTAB, 56 | SHT_STRTAB, 57 | SHT_RELA, 58 | SHT_HASH, 59 | SHT_DYNAMIC 60 | }; 61 | 62 | typedef struct { 63 | uint8_t *data; 64 | uint64_t offset; 65 | uint64_t length; 66 | } dol_t; 67 | 68 | #pragma pack(1) 69 | typedef struct { 70 | uint8_t e_ident[16]; 71 | uint16_t e_type; 72 | uint16_t e_machine; 73 | uint32_t e_version; 74 | uint64_t e_entry; 75 | uint64_t e_phoff; 76 | uint64_t e_shoff; 77 | uint32_t e_flags; 78 | uint16_t e_ehsize; 79 | uint16_t e_phentsize; 80 | uint16_t e_phnum; 81 | uint16_t e_shentsize; 82 | uint16_t e_shnum; 83 | uint16_t e_shstrndx; 84 | } Elf64_Ehdr; 85 | 86 | typedef struct { 87 | uint32_t sh_name; 88 | uint32_t sh_type; 89 | uint64_t sh_flags; 90 | uint64_t sh_addr; 91 | uint64_t sh_offset; 92 | uint64_t sh_size; 93 | uint32_t sh_link; 94 | uint32_t sh_info; 95 | uint64_t sh_addralign; 96 | uint64_t sh_entsize; 97 | } ELF64_Shdr; 98 | 99 | typedef struct { 100 | uint64_t tag; 101 | uint64_t val; 102 | } ELF64_Dyn; 103 | 104 | typedef struct { 105 | uint32_t unit_length; 106 | uint16_t version; 107 | uint32_t debug_abbrev_offset; 108 | uint8_t address_size; 109 | } DWARF32_CUHdr; 110 | 111 | typedef struct { 112 | uint8_t min_inst_length; 113 | uint8_t max_ops_per_inst; 114 | uint8_t default_is_stmt; 115 | int8_t line_base; 116 | uint8_t line_range; 117 | uint8_t opcode_base; 118 | } DWARF_V4_LineHdr; 119 | 120 | typedef struct { 121 | uint8_t min_inst_length; 122 | uint8_t default_is_stmt; 123 | int8_t line_base; 124 | uint8_t line_range; 125 | uint8_t opcode_base; 126 | } DWARF_V3_LineHdr; 127 | #pragma pack() 128 | 129 | typedef struct { 130 | uint8_t min_inst_length; 131 | uint8_t max_ops_per_inst; 132 | uint8_t default_is_stmt; 133 | int8_t line_base; 134 | uint8_t line_range; 135 | uint8_t opcode_base; 136 | } DWARF_LineHdr; 137 | 138 | typedef struct { 139 | uint64_t id; 140 | uint64_t cu_idx; 141 | 142 | uint32_t type; 143 | uint8_t has_children; 144 | 145 | uint8_t *attr_buf; 146 | int attr_count; 147 | } AbbrevUnit; 148 | 149 | typedef struct { 150 | uint8_t has_children; 151 | int depth; 152 | 153 | uint64_t parent_idx; 154 | uint64_t cu_idx; 155 | 156 | uint8_t *attr_buf; 157 | int attr_count; 158 | 159 | uint32_t type; 160 | uint64_t type_idx; 161 | uint64_t au_offset; 162 | uint64_t type_offset; 163 | 164 | char *name; 165 | char *comp_dir; 166 | 167 | uint8_t *loc_expr; 168 | int loc_expr_len; 169 | uint8_t *loc_list; 170 | uint64_t const_val; 171 | 172 | uint8_t *frame_base_expr; 173 | int frame_base_expr_len; 174 | 175 | uint32_t type_width; 176 | uint32_t type_encoding; 177 | 178 | uint32_t member_offset; 179 | bool member_offset_present; 180 | 181 | uint64_t low_pc; 182 | uint64_t high_pc; 183 | } Block; 184 | 185 | typedef struct { 186 | uint64_t address; 187 | uint32_t op_idx; 188 | uint32_t file_idx; 189 | uint32_t line_num; 190 | uint32_t col_num; 191 | bool is_stmt; 192 | bool basic_block; 193 | bool end_sequence; 194 | bool prologue_end; 195 | bool epilogue_begin; 196 | uint32_t isa; 197 | uint32_t discriminator; 198 | } LineMachine; 199 | 200 | typedef struct { 201 | char *filenames[1024]; 202 | int file_count; 203 | uint64_t dir_idx[1024]; 204 | char *dirs[1024]; 205 | int dir_count; 206 | uint8_t *op_buf; 207 | uint32_t op_buf_size; 208 | bool default_is_stmt; 209 | int8_t line_base; 210 | uint8_t line_range; 211 | uint8_t opcode_base; 212 | 213 | LineMachine *lines; 214 | int line_count; 215 | int line_max; 216 | } CULineTable; 217 | 218 | typedef struct { 219 | uint64_t address; 220 | uint64_t orig_data; 221 | uint64_t ref_count; 222 | } Breakpoint; 223 | 224 | typedef struct { 225 | uint8_t *old_data; 226 | uint8_t *new_data; 227 | 228 | uint64_t member_idx; 229 | uint64_t member_offset; 230 | 231 | uint64_t watch_width; 232 | uint64_t end; 233 | 234 | // Block idx 235 | uint64_t var_idx; 236 | 237 | // Breakpoint idx 238 | uint64_t break_idx; 239 | } Watchpoint; 240 | 241 | typedef enum { 242 | HWNone = 0x0, 243 | HWBreakpoint, 244 | HWWatchpoint, 245 | } HWBreakType; 246 | 247 | typedef struct { 248 | uint64_t addr; 249 | 250 | uint64_t idx; 251 | HWBreakType type; 252 | } HWBreak; 253 | 254 | 255 | typedef enum { 256 | DWString, 257 | DWIVal, 258 | DWUVal, 259 | DWOffset, 260 | DWAddr, 261 | DWExpr 262 | } DWType; 263 | 264 | typedef struct { 265 | DWType type; 266 | union { 267 | uint64_t val; 268 | uint8_t *expr; 269 | char *str; 270 | } data; 271 | int size; 272 | int skip; 273 | } DWResult; 274 | 275 | typedef struct { 276 | uint64_t var_idx; 277 | uint64_t width; 278 | 279 | uint64_t member_idx; 280 | uint64_t member_offset; 281 | } VarInfo; 282 | 283 | typedef struct { 284 | uint8_t *file_buffer; 285 | uint64_t file_size; 286 | 287 | char *bin_name; 288 | char **args; 289 | char cur_dir[PATH_MAX + 1]; 290 | 291 | uint8_t *strtab; 292 | int strtab_size; 293 | 294 | uint8_t *dynamic; 295 | int dynamic_size; 296 | 297 | uint8_t *dynstr; 298 | int dynstr_size; 299 | 300 | uint8_t *debug_info; 301 | int debug_info_size; 302 | 303 | uint8_t *debug_line; 304 | int debug_line_size; 305 | 306 | uint8_t *debug_str; 307 | int debug_str_size; 308 | 309 | uint8_t *debug_abbrev; 310 | int debug_abbrev_size; 311 | 312 | uint8_t *debug_loc; 313 | int debug_loc_size; 314 | } DWSections; 315 | 316 | typedef struct { 317 | uint8_t *expr; 318 | uint64_t len; 319 | } DWExpression; 320 | 321 | typedef struct { 322 | uint64_t val_stack[VAL_STACK_MAX]; 323 | uint64_t val_stack_len; 324 | 325 | Block *frame_holder; 326 | } DWExprMachine; 327 | 328 | typedef struct { 329 | uint64_t start; 330 | uint64_t end; 331 | } FuncFrame; 332 | 333 | typedef struct { 334 | uint64_t framed_scope_idx; 335 | uint64_t scope_idx; 336 | } CurrentScopes; 337 | 338 | typedef struct { 339 | DWSections sections; 340 | 341 | Block *block_table; 342 | uint64_t block_len; 343 | uint64_t block_max; 344 | 345 | CULineTable *line_tables; 346 | uint64_t line_tables_len; 347 | uint64_t line_tables_max; 348 | 349 | Breakpoint *break_table; 350 | uint64_t break_len; 351 | uint64_t break_max; 352 | 353 | Watchpoint *watch_table; 354 | uint64_t watch_len; 355 | uint64_t watch_max; 356 | 357 | bool needs_restart; 358 | 359 | bool should_reset; 360 | uint64_t reset_idx; 361 | 362 | bool sw_watch_enabled; 363 | HWBreak hw_slots[HW_SLOTS_MAX]; 364 | uint64_t hw_slots_max; 365 | } DebugState; 366 | 367 | #define DWARF_FORM \ 368 | X(DW_FORM_addr, 0x01) \ 369 | X(DW_FORM_block2, 0x03) \ 370 | X(DW_FORM_block4, 0x04) \ 371 | X(DW_FORM_data2, 0x05) \ 372 | X(DW_FORM_data4, 0x06) \ 373 | X(DW_FORM_data8, 0x07) \ 374 | X(DW_FORM_string, 0x08) \ 375 | X(DW_FORM_block, 0x09) \ 376 | X(DW_FORM_block1, 0x0a) \ 377 | X(DW_FORM_data1, 0x0b) \ 378 | X(DW_FORM_flag, 0x0c) \ 379 | X(DW_FORM_sdata, 0x0d) \ 380 | X(DW_FORM_strp, 0x0e) \ 381 | X(DW_FORM_udata, 0x0f) \ 382 | X(DW_FORM_ref_addr, 0x10) \ 383 | X(DW_FORM_ref1, 0x11) \ 384 | X(DW_FORM_ref2, 0x12) \ 385 | X(DW_FORM_ref4, 0x13) \ 386 | X(DW_FORM_ref8, 0x14) \ 387 | X(DW_FORM_ref_udata, 0x15) \ 388 | X(DW_FORM_indirect, 0x16) \ 389 | X(DW_FORM_sec_offset, 0x17) \ 390 | X(DW_FORM_exprloc, 0x18) \ 391 | X(DW_FORM_flag_present, 0x19) 392 | 393 | #define DWARF_TAG \ 394 | X(DW_TAG_array_type, 0x01) \ 395 | X(DW_TAG_formal_parameter, 0x05) \ 396 | X(DW_TAG_lexical_block, 0x0b) \ 397 | X(DW_TAG_member, 0x0d) \ 398 | X(DW_TAG_pointer_type, 0x0f) \ 399 | X(DW_TAG_compile_unit, 0x11) \ 400 | X(DW_TAG_structure_type, 0x13) \ 401 | X(DW_TAG_typedef, 0x16) \ 402 | X(DW_TAG_inlined_subroutine, 0x1d) \ 403 | X(DW_TAG_subrange_type, 0x21) \ 404 | X(DW_TAG_subprogram, 0x2e) \ 405 | X(DW_TAG_base_type, 0x24) \ 406 | X(DW_TAG_variable, 0x34) \ 407 | X(DW_TAG_program, 0xFF) \ 408 | 409 | #define DWARF_ATE \ 410 | X(DW_ATE_address, 0x01) \ 411 | X(DW_ATE_boolean, 0x02) \ 412 | X(DW_ATE_complex_float, 0x03) \ 413 | X(DW_ATE_float, 0x04) \ 414 | X(DW_ATE_signed, 0x05) \ 415 | X(DW_ATE_signed_char, 0x06) \ 416 | X(DW_ATE_unsigned, 0x07) \ 417 | X(DW_ATE_unsigned_char, 0x08) \ 418 | X(DW_ATE_imaginary_float, 0x09) \ 419 | X(DW_ATE_packed_decimal, 0x0a) \ 420 | X(DW_ATE_numeric_string, 0x0b) \ 421 | X(DW_ATE_edited, 0x0c) \ 422 | X(DW_ATE_signed_fixed, 0x0d) \ 423 | X(DW_ATE_unsigned_fixed, 0x0e) \ 424 | X(DW_ATE_decimal_float, 0x0f) \ 425 | X(DW_ATE_UTF, 0x10) \ 426 | X(DW_ATE_lo_user, 0x80) \ 427 | X(DW_ATE_hi_user, 0xff) \ 428 | 429 | #define DWARF_AT \ 430 | X(DW_AT_location, 0x02) \ 431 | X(DW_AT_name, 0x03) \ 432 | X(DW_AT_byte_size, 0x0b) \ 433 | X(DW_AT_stmt_list, 0x10) \ 434 | X(DW_AT_low_pc, 0x11) \ 435 | X(DW_AT_high_pc, 0x12) \ 436 | X(DW_AT_language, 0x13) \ 437 | X(DW_AT_comp_dir, 0x1b) \ 438 | X(DW_AT_const_value, 0x1c) \ 439 | X(DW_AT_inline, 0x20) \ 440 | X(DW_AT_producer, 0x25) \ 441 | X(DW_AT_prototyped, 0x27) \ 442 | X(DW_AT_abstract_origin, 0x31) \ 443 | X(DW_AT_count, 0x37) \ 444 | X(DW_AT_data_member_location, 0x38) \ 445 | X(DW_AT_decl_file, 0x3a) \ 446 | X(DW_AT_decl_line, 0x3b) \ 447 | X(DW_AT_encoding, 0x3e) \ 448 | X(DW_AT_external, 0x3f) \ 449 | X(DW_AT_frame_base, 0x40) \ 450 | X(DW_AT_type, 0x49) \ 451 | X(DW_AT_ranges, 0x55) \ 452 | X(DW_AT_call_column, 0x57) \ 453 | X(DW_AT_call_file, 0x58) \ 454 | X(DW_AT_call_line, 0x59) 455 | 456 | #define DWARF_LINE \ 457 | X(DW_LNE_end_sequence, 0x01) \ 458 | X(DW_LNE_set_address, 0x02) 459 | 460 | #define DWARF_LNS \ 461 | X(DW_LNS_copy, 0x01) \ 462 | X(DW_LNS_advance_pc, 0x02) \ 463 | X(DW_LNS_advance_line, 0x03) \ 464 | X(DW_LNS_set_file, 0x04) \ 465 | X(DW_LNS_set_column, 0x05) \ 466 | X(DW_LNS_negate_stmt, 0x06) \ 467 | X(DW_LNS_const_add_pc, 0x08) \ 468 | X(DW_LNS_set_prologue_end, 0x0a) 469 | 470 | #define DWARF_OP \ 471 | X(DW_OP_addr, 0x03) \ 472 | X(DW_OP_deref, 0x06) \ 473 | X(DW_OP_const1u, 0x08) \ 474 | X(DW_OP_const1s, 0x09) \ 475 | X(DW_OP_const2u, 0x0a) \ 476 | X(DW_OP_const2s, 0x0b) \ 477 | X(DW_OP_const4u, 0x0c) \ 478 | X(DW_OP_const4s, 0x0d) \ 479 | X(DW_OP_const8u, 0x0e) \ 480 | X(DW_OP_const8s, 0x0f) \ 481 | X(DW_OP_constu, 0x10) \ 482 | X(DW_OP_consts, 0x11) \ 483 | X(DW_OP_dup, 0x12) \ 484 | X(DW_OP_drop, 0x13) \ 485 | X(DW_OP_over, 0x14) \ 486 | X(DW_OP_pick, 0x15) \ 487 | X(DW_OP_swap, 0x16) \ 488 | X(DW_OP_rot, 0x17) \ 489 | X(DW_OP_xderef, 0x18) \ 490 | X(DW_OP_abs, 0x19) \ 491 | X(DW_OP_and, 0x1a) \ 492 | X(DW_OP_div, 0x1b) \ 493 | X(DW_OP_minus, 0x1c) \ 494 | X(DW_OP_mod, 0x1d) \ 495 | X(DW_OP_mul, 0x1e) \ 496 | X(DW_OP_neg, 0x1f) \ 497 | X(DW_OP_not, 0x20) \ 498 | X(DW_OP_or, 0x21) \ 499 | X(DW_OP_plus, 0x22) \ 500 | X(DW_OP_plus_uconst, 0x23) \ 501 | X(DW_OP_shl, 0x24) \ 502 | X(DW_OP_shr, 0x25) \ 503 | X(DW_OP_shra, 0x26) \ 504 | X(DW_OP_xor, 0x27) \ 505 | X(DW_OP_bra, 0x28) \ 506 | X(DW_OP_eq, 0x29) \ 507 | X(DW_OP_ge, 0x2a) \ 508 | X(DW_OP_gt, 0x2b) \ 509 | X(DW_OP_le, 0x2c) \ 510 | X(DW_OP_lt, 0x2d) \ 511 | X(DW_OP_ne, 0x2e) \ 512 | X(DW_OP_skip, 0x2f) \ 513 | X(DW_OP_regx, 0x90) \ 514 | X(DW_OP_fbreg, 0x91) \ 515 | 516 | #define X(name, value) name = value, 517 | enum { DWARF_FORM }; 518 | enum { DWARF_TAG }; 519 | enum { DWARF_AT }; 520 | enum { DWARF_ATE }; 521 | enum { DWARF_LINE }; 522 | enum { DWARF_LNS }; 523 | enum { DWARF_OP }; 524 | #undef X 525 | 526 | 527 | uint64_t get_regval_for_dwarf_reg(struct user_regs_struct *regs, uint8_t idx) { 528 | switch (idx) { 529 | case 0: return regs->rax; 530 | case 1: return regs->rdx; 531 | case 2: return regs->rcx; 532 | case 3: return regs->rbx; 533 | case 4: return regs->rsi; 534 | case 5: return regs->rdi; 535 | case 6: return regs->rbp; 536 | case 7: return regs->rsp; 537 | case 8: return regs->r8; 538 | case 9: return regs->r9; 539 | case 10: return regs->r10; 540 | case 11: return regs->r11; 541 | case 12: return regs->r12; 542 | case 13: return regs->r13; 543 | case 14: return regs->r14; 544 | case 15: return regs->r15; 545 | // case 16: return regs->ra; 546 | /* 547 | case 17: return regs->xmm0; 548 | case 18: return regs->xmm1; 549 | case 19: return regs->xmm2; 550 | case 20: return regs->xmm3; 551 | case 21: return regs->xmm4; 552 | case 22: return regs->xmm5; 553 | case 23: return regs->xmm6; 554 | case 24: return regs->xmm7; 555 | case 25: return regs->xmm8; 556 | case 26: return regs->xmm9; 557 | case 27: return regs->xmm10; 558 | case 28: return regs->xmm11; 559 | case 29: return regs->xmm12; 560 | case 30: return regs->xmm13; 561 | case 31: return regs->xmm14; 562 | case 32: return regs->xmm15; 563 | */ 564 | } 565 | return 0; 566 | } 567 | 568 | uint64_t get_time_ms() { 569 | struct timespec tms; 570 | clock_gettime(CLOCK_MONOTONIC, &tms); 571 | 572 | uint64_t millis = tms.tv_sec * 1000; 573 | millis += tms.tv_nsec / 1000000; 574 | return millis; 575 | } 576 | 577 | int64_t get_leb128_i(uint8_t *buf, uint32_t *size) { 578 | uint8_t *ptr = buf; 579 | int64_t result = 0; 580 | uint32_t shift = 0; 581 | 582 | uint8_t byte; 583 | do { 584 | byte = *ptr++; 585 | result |= ((uint64_t)(byte & 0x7f)) << shift; 586 | shift += 7; 587 | } while (byte >= 128); 588 | 589 | if (shift < 64 && (byte & 0x40)) { 590 | result |= (~0) << shift; 591 | } 592 | 593 | *size = (uint32_t)(ptr - buf); 594 | 595 | return result; 596 | } 597 | 598 | uint64_t get_leb128_u(uint8_t *buf, uint32_t *size) { 599 | uint8_t *ptr = buf; 600 | int64_t result = 0; 601 | uint32_t shift = 0; 602 | 603 | uint8_t byte; 604 | do { 605 | byte = *ptr++; 606 | result |= ((uint64_t)(byte & 0x7f)) << shift; 607 | shift += 7; 608 | } while (byte >= 128); 609 | 610 | *size = (uint32_t)(ptr - buf); 611 | 612 | return result; 613 | } 614 | 615 | void init_line_machine(LineMachine *lm, bool is_stmt) { 616 | lm->address = 0; 617 | lm->op_idx = 0; 618 | lm->file_idx = 1; 619 | lm->line_num = 1; 620 | lm->col_num = 0; 621 | lm->basic_block = false; 622 | lm->end_sequence = false; 623 | lm->prologue_end = false; 624 | lm->epilogue_begin = false; 625 | lm->isa = 0; 626 | lm->discriminator = 0; 627 | lm->is_stmt = is_stmt; 628 | } 629 | 630 | char *dwarf_tag_to_str(uint8_t id) { 631 | switch (id) { 632 | case 0: return "0"; 633 | #define X(name, value) case name: return #name; 634 | DWARF_TAG 635 | #undef X 636 | default: { 637 | if (id > 0x80) { 638 | panic("TODO Error on tag (0x%x) We don't actually handle LEB128\n", id); 639 | } 640 | return "(unknown)"; 641 | } 642 | } 643 | } 644 | 645 | char *dwarf_type_to_str(uint8_t type) { 646 | switch (type) { 647 | #define X(name, value) case name: return #name; 648 | DWARF_ATE 649 | #undef X 650 | default: { 651 | return "(unknown)"; 652 | } 653 | } 654 | } 655 | 656 | char *dwarf_attr_name_to_str(uint8_t attr_name) { 657 | switch (attr_name) { 658 | case 0: return "0"; 659 | #define X(name, value) case name: return #name; 660 | DWARF_AT 661 | #undef X 662 | default: { 663 | if (attr_name > 0x80) { 664 | panic("TODO Error on attr_name (0x%x) We don't actually handle LEB128\n", attr_name); 665 | } 666 | return "(unknown)"; 667 | } 668 | } 669 | } 670 | 671 | char *dwarf_attr_form_to_str(uint8_t attr_form) { 672 | switch (attr_form) { 673 | case 0: return "0"; 674 | #define X(name, value) case name: return #name; 675 | DWARF_FORM 676 | #undef X 677 | default: { 678 | if (attr_form > 0x80) { 679 | panic("TODO Error on attr_form (0x%x) We don't actually handle LEB128\n", attr_form); 680 | } 681 | return "(unknown)"; 682 | } 683 | } 684 | } 685 | 686 | char *dwarf_line_ext_op_to_str(uint8_t op) { 687 | switch (op) { 688 | #define X(name, value) case name: return #name; 689 | DWARF_LINE 690 | #undef X 691 | default: { 692 | if (op > 0x80) { 693 | panic("TODO Error on line extended op (0x%x) We don't actually handle LEB128\n", op); 694 | } 695 | return "(unknown)"; 696 | } 697 | } 698 | } 699 | 700 | char *dwarf_line_op_to_str(uint8_t op) { 701 | switch (op) { 702 | #define X(name, value) case name: return #name; 703 | DWARF_LNS 704 | #undef X 705 | default: { 706 | if (op > 0x80) { 707 | panic("TODO Error on line op (0x%x) We don't actually handle LEB128\n", op); 708 | } 709 | return "(unknown)"; 710 | } 711 | } 712 | } 713 | 714 | char *dwarf_expr_op_to_str(uint8_t expr_op) { 715 | static char op_str_cache[20]; 716 | 717 | if (expr_op >= 0x30 && expr_op <= 0x4f) { 718 | sprintf(op_str_cache, "DW_OP_lit%d", expr_op - 0x30); 719 | return op_str_cache; 720 | } 721 | 722 | if (expr_op >= 0x50 && expr_op <= 0x6f) { 723 | sprintf(op_str_cache, "DW_OP_reg%d", expr_op - 0x50); 724 | return op_str_cache; 725 | } 726 | 727 | if (expr_op >= 0x71 && expr_op <= 0x8f) { 728 | sprintf(op_str_cache, "DW_OP_breg%d", expr_op - 0x71); 729 | return op_str_cache; 730 | } 731 | 732 | switch (expr_op) { 733 | #define X(name, value) case name: return #name; 734 | DWARF_OP 735 | #undef X 736 | default: return "(unknown)"; 737 | } 738 | } 739 | 740 | void print_abbrev_table(AbbrevUnit *entries, int entry_count) { 741 | for (int i = 0; i < entry_count; i++) { 742 | AbbrevUnit *entry = &entries[i]; 743 | printf("Entry ID: %lu || CU: %lu\n", entry->id, entry->cu_idx); 744 | printf(" - type: %s (0x%x)\n", dwarf_tag_to_str(entry->type), entry->type); 745 | printf(" - children: %s\n", ((entry->has_children) ? "yes" : "no")); 746 | printf(" - attributes: %d\n", entry->attr_count); 747 | 748 | for (int j = 0; j < (entry->attr_count * 2); j += 2) { 749 | uint8_t attr_name = entry->attr_buf[j]; 750 | uint8_t attr_form = entry->attr_buf[j + 1]; 751 | printf("(0x%02x) %-18s (0x%02x) %s\n", attr_name, dwarf_attr_name_to_str(attr_name), attr_form, dwarf_attr_form_to_str(attr_form)); 752 | } 753 | } 754 | } 755 | 756 | void print_line_table(CULineTable *line_tables, uint64_t line_tables_len) { 757 | for (uint64_t i = 0; i < line_tables_len; i++) { 758 | CULineTable *line_table = &line_tables[i]; 759 | printf("CU Line Table %lu\n", i + 1); 760 | printf("Files:\n"); 761 | for (int j = 0; j < line_table->file_count; j++) { 762 | uint8_t dir_idx = line_table->dir_idx[j]; 763 | char *dirname = line_table->dirs[dir_idx]; 764 | if (!line_table->filenames[j]) { 765 | continue; 766 | } 767 | 768 | printf("[%d] - %s | %s\n", j, dirname, line_table->filenames[j]); 769 | } 770 | /* 771 | for (int j = 0; j < line_table->line_count; j++) { 772 | LineMachine *line = &line_table->lines[j]; 773 | printf("line %u | address 0x%lx | file %s\n", 774 | line->line_num, line->address, line_table->filenames[line->file_idx]); 775 | } 776 | */ 777 | printf("opcode base: %d\n", line_table->opcode_base); 778 | printf("line base: %d\n", line_table->line_base); 779 | printf("line range: %d\n", line_table->line_range); 780 | printf("default is_stmt: %d\n", line_table->default_is_stmt); 781 | printf("\n"); 782 | } 783 | } 784 | 785 | void print_block_table(Block *block_table, uint64_t block_len) { 786 | for (uint64_t i = 0; i < block_len; i++) { 787 | Block *block = &block_table[i]; 788 | int indent_width = block->depth * 4; 789 | printf("%*c<0x%lx> [%lu] %s\n", indent_width, ' ', block->au_offset, i, dwarf_tag_to_str(block->type)); 790 | 791 | if (block->name) { 792 | printf("%*c- name: %s\n", indent_width, ' ', block->name); 793 | } 794 | if (block->comp_dir) { 795 | printf("%*c- path: %s/%s\n", indent_width, ' ', block->comp_dir, block->name); 796 | } 797 | if (block->low_pc && block->high_pc) { 798 | printf("%*c- range: 0x%lx - 0x%lx\n", indent_width, ' ', block->low_pc, block->high_pc + block->low_pc); 799 | } 800 | if (block->frame_base_expr && block->frame_base_expr_len) { 801 | printf("%*c- frame base expr: ", indent_width, ' '); 802 | for (int j = 0; j < block->frame_base_expr_len; j++) { 803 | printf("%x ", block->frame_base_expr[j]); 804 | } 805 | printf("\n"); 806 | } 807 | if (block->loc_expr && block->loc_expr_len) { 808 | printf("%*c- location expr: ", indent_width, ' '); 809 | for (int j = 0; j < block->loc_expr_len; j++) { 810 | printf("%x ", block->loc_expr[j]); 811 | } 812 | printf("\n"); 813 | } 814 | if (block->type_offset) { 815 | printf("%*c- type offset: <0x%lx>\n", indent_width, ' ', block->type_offset); 816 | } 817 | if (block->member_offset_present) { 818 | printf("%*c- member offset: <0x%x>\n", indent_width, ' ', block->member_offset); 819 | } 820 | if (block->type_width) { 821 | printf("%*c- type width: %u\n", indent_width, ' ', block->type_width); 822 | } 823 | if (block->type_encoding) { 824 | printf("%*c- type encoding: %s\n", indent_width, ' ', dwarf_type_to_str(block->type_encoding)); 825 | } 826 | } 827 | } 828 | 829 | int open_binary(char *name, char **prog_path) { 830 | int fd; 831 | 832 | fd = open(name, O_RDONLY); 833 | if (fd >= 0) { 834 | strcpy(*prog_path, name); 835 | return fd; 836 | 837 | } 838 | close(fd); 839 | 840 | char *path = getenv("PATH"); 841 | char *tmp = NULL; 842 | do { 843 | tmp = strchr(path, ':'); 844 | if (tmp != NULL) { 845 | tmp[0] = 0; 846 | } 847 | 848 | uint64_t path_len = snprintf(*prog_path, PATH_MAX, "%s/%s", path, name); 849 | fd = open(*prog_path, O_RDONLY); 850 | if (fd >= 0) { 851 | return fd; 852 | } 853 | 854 | close(fd); 855 | 856 | memset(*prog_path, 0, path_len); 857 | path = tmp + 1; 858 | } while (tmp != NULL); 859 | 860 | return -1; 861 | } 862 | 863 | DWResult parse_data(DWSections *sections, int data_idx, uint8_t *attr_elem) { 864 | uint64_t entry_size = 0; 865 | uint32_t leb_size = 0; 866 | uint64_t attr_name = get_leb128_u(attr_elem, &leb_size); 867 | if (!leb_size) { 868 | panic("failed to parse leb!\n"); 869 | } 870 | entry_size += leb_size; 871 | 872 | leb_size = 0; 873 | uint64_t attr_form = get_leb128_u(attr_elem + entry_size, &leb_size); 874 | if (!leb_size) { 875 | panic("failed to parse leb!\n"); 876 | } 877 | 878 | uint8_t *debug_info_ptr = sections->debug_info + data_idx; 879 | DWResult ret = {0}; 880 | 881 | switch (attr_form) { 882 | case DW_FORM_string: { 883 | char *str = (char *)debug_info_ptr; 884 | uint64_t str_len = strlen(str); 885 | 886 | ret.type = DWString; 887 | ret.data.str = str; 888 | ret.skip = str_len + 1; 889 | } break; 890 | case DW_FORM_strp: { 891 | uint32_t str_off = *((uint32_t *)debug_info_ptr); 892 | char *str = (char *)(sections->debug_str + str_off); 893 | 894 | 895 | ret.type = DWString; 896 | ret.data.str = str; 897 | ret.skip = sizeof(uint32_t); 898 | } break; 899 | case DW_FORM_addr: { 900 | uint64_t addr = *((uint64_t *)debug_info_ptr); 901 | 902 | ret.type = DWAddr; 903 | ret.data.val = addr; 904 | ret.skip = sizeof(uint64_t); 905 | } break; 906 | case DW_FORM_data1: { 907 | uint8_t data = *debug_info_ptr; 908 | 909 | ret.type = DWUVal; 910 | ret.data.val = data; 911 | ret.skip = sizeof(uint8_t); 912 | } break; 913 | case DW_FORM_data2: { 914 | uint16_t data = *((uint16_t *)debug_info_ptr); 915 | 916 | ret.type = DWUVal; 917 | ret.data.val = data; 918 | ret.skip = sizeof(uint16_t); 919 | } break; 920 | case DW_FORM_data4: { 921 | uint32_t data = *((uint32_t *)debug_info_ptr); 922 | 923 | ret.type = DWUVal; 924 | ret.data.val = data; 925 | ret.skip = sizeof(uint32_t); 926 | } break; 927 | case DW_FORM_sdata: { 928 | uint32_t leb_size = 0; 929 | uint64_t sdata = get_leb128_i(debug_info_ptr, &leb_size); 930 | 931 | ret.type = DWIVal; 932 | ret.data.val = sdata; 933 | ret.skip = leb_size; 934 | } break; 935 | case DW_FORM_udata: { 936 | uint32_t leb_size = 0; 937 | uint64_t udata = get_leb128_u(debug_info_ptr, &leb_size); 938 | 939 | ret.type = DWUVal; 940 | ret.data.val = udata; 941 | ret.skip = leb_size; 942 | } break; 943 | case DW_FORM_ref4: { 944 | uint32_t offset = *((uint32_t *)debug_info_ptr); 945 | 946 | ret.type = DWOffset; 947 | ret.data.val = offset; 948 | ret.skip = sizeof(uint32_t); 949 | } break; 950 | case DW_FORM_sec_offset: { 951 | uint32_t sect_off = *((uint32_t *)debug_info_ptr); 952 | ret.type = DWUVal; 953 | ret.data.val = sect_off; 954 | ret.skip = sizeof(uint32_t); 955 | } break; 956 | case DW_FORM_exprloc: { 957 | uint32_t leb_size = 0; 958 | uint64_t length = get_leb128_u(debug_info_ptr, &leb_size); 959 | 960 | ret.type = DWExpr; 961 | ret.data.expr = debug_info_ptr + leb_size; 962 | ret.skip = leb_size + length; 963 | ret.size = length; 964 | } break; 965 | case DW_FORM_flag_present: { 966 | ret.type = DWUVal; 967 | ret.data.val = 1; 968 | } break; 969 | default: { 970 | printf("(0x%02lx) %-18s (0x%02lx) %s\n", attr_name, dwarf_attr_name_to_str(attr_name), attr_form, dwarf_attr_form_to_str(attr_form)); 971 | panic("Unhandled form: (0x%02lx) %s!\n", attr_form, dwarf_attr_form_to_str(attr_form)); 972 | } 973 | } 974 | 975 | return ret; 976 | } 977 | 978 | uint64_t get_file_size(int fd) { 979 | off_t f_end = lseek(fd, 0, SEEK_END); 980 | if (f_end < 0) { 981 | panic("Failed to seek to end of file!\n"); 982 | } 983 | lseek(fd, 0, SEEK_SET); 984 | 985 | return f_end; 986 | } 987 | 988 | void load_elf_sections(DWSections *sections, char *file_path) { 989 | if (getcwd(sections->cur_dir, sizeof(sections->cur_dir)) == NULL) { 990 | panic("Failed to get current dir!\n"); 991 | } 992 | 993 | sections->bin_name = malloc(PATH_MAX + 1); 994 | sections->bin_name[PATH_MAX] = '\0'; 995 | 996 | int fd = open_binary(file_path, §ions->bin_name); 997 | if (fd < 0) { 998 | panic("Failed to open program %s\n", file_path); 999 | } 1000 | 1001 | printf("Debugging %s\n", sections->bin_name); 1002 | 1003 | uint64_t size = get_file_size(fd); 1004 | sections->file_buffer = malloc(size); 1005 | sections->file_size = size; 1006 | 1007 | ssize_t ret = read(fd, sections->file_buffer, size); 1008 | if (ret < 0 || (uint64_t)ret != size) { 1009 | panic("Failed to read %s\n", sections->bin_name); 1010 | } 1011 | close(fd); 1012 | 1013 | Elf64_Ehdr *elf_hdr = (Elf64_Ehdr *)sections->file_buffer; 1014 | if (!(elf_hdr->e_ident[0] == 0x7f && 1015 | elf_hdr->e_ident[1] == 'E' && 1016 | elf_hdr->e_ident[2] == 'L' && 1017 | elf_hdr->e_ident[3] == 'F')) { 1018 | panic("File is not a valid ELF file\n"); 1019 | } 1020 | 1021 | #define ELFCLASS64 2 1022 | #define ELFDATA2LSB 1 1023 | #define EM_X86_64 62 1024 | if (elf_hdr->e_ident[4] != ELFCLASS64 || 1025 | elf_hdr->e_ident[5] != ELFDATA2LSB || 1026 | elf_hdr->e_machine != EM_X86_64) { 1027 | panic("This debugger only supports x86_64\n"); 1028 | } 1029 | 1030 | #define ET_EXEC 2 1031 | #define ET_DYN 3 1032 | #define ET_CORE 4 1033 | 1034 | if (elf_hdr->e_type == ET_CORE) { 1035 | panic("This debugger doesn't support core dumps yet!\n"); 1036 | } 1037 | 1038 | if (!(elf_hdr->e_type == ET_EXEC || elf_hdr->e_type == ET_DYN)) { 1039 | panic("This debugger only supports executable files\n"); 1040 | } 1041 | 1042 | if (!elf_hdr->e_shstrndx) { 1043 | panic("TODO This debugger requires symbols to be able to debug the file\n"); 1044 | } 1045 | 1046 | uint8_t *sect_hdr_buf = sections->file_buffer + elf_hdr->e_shoff; 1047 | ELF64_Shdr *strtable_hdr = (ELF64_Shdr *)(sect_hdr_buf + (elf_hdr->e_shstrndx * elf_hdr->e_shentsize)); 1048 | if (strtable_hdr->sh_type != SHT_STRTAB) { 1049 | panic("Executable string table appears invalid!\n"); 1050 | } 1051 | 1052 | char *strtable = (char *)(sections->file_buffer + strtable_hdr->sh_offset); 1053 | for (int i = 0; i < elf_hdr->e_shnum; i++) { 1054 | ELF64_Shdr *sect_hdr = (ELF64_Shdr *)(sect_hdr_buf + (i * elf_hdr->e_shentsize)); 1055 | 1056 | char *section_name = strtable + sect_hdr->sh_name; 1057 | 1058 | char dbginfo_str[] = ".debug_info"; 1059 | char dbgabbrev_str[] = ".debug_abbrev"; 1060 | char dbgstr_str[] = ".debug_str"; 1061 | char dbgline_str[] = ".debug_line"; 1062 | char dbgloc_str[] = ".debug_loc"; 1063 | char dynamic_str[] = ".dynamic"; 1064 | char dynstr_str[] = ".dynstr"; 1065 | char strtab_str[] = ".strtab"; 1066 | 1067 | if (!(memcmp(section_name, dbginfo_str, sizeof(dbginfo_str)))) { 1068 | sections->debug_info_size = sect_hdr->sh_size; 1069 | sections->debug_info = sections->file_buffer + sect_hdr->sh_offset; 1070 | } else if (!(memcmp(section_name, dbgabbrev_str, sizeof(dbgabbrev_str)))) { 1071 | sections->debug_abbrev_size = sect_hdr->sh_size; 1072 | sections->debug_abbrev = sections->file_buffer + sect_hdr->sh_offset; 1073 | } else if (!(memcmp(section_name, dbgstr_str, sizeof(dbgstr_str)))) { 1074 | sections->debug_str_size = sect_hdr->sh_size; 1075 | sections->debug_str = sections->file_buffer + sect_hdr->sh_offset; 1076 | } else if (!(memcmp(section_name, dbgline_str, sizeof(dbgline_str)))) { 1077 | sections->debug_line_size = sect_hdr->sh_size; 1078 | sections->debug_line = sections->file_buffer + sect_hdr->sh_offset; 1079 | } else if (!(memcmp(section_name, dynamic_str, sizeof(dynamic_str)))) { 1080 | sections->dynamic_size = sect_hdr->sh_size; 1081 | sections->dynamic = sections->file_buffer + sect_hdr->sh_offset; 1082 | } else if (!(memcmp(section_name, dynstr_str, sizeof(dynstr_str)))) { 1083 | sections->dynstr_size = sect_hdr->sh_size; 1084 | sections->dynstr = sections->file_buffer + sect_hdr->sh_offset; 1085 | } else if (!(memcmp(section_name, strtab_str, sizeof(strtab_str)))) { 1086 | sections->strtab_size = sect_hdr->sh_size; 1087 | sections->strtab = sections->file_buffer + sect_hdr->sh_offset; 1088 | } else if (!(memcmp(section_name, dbgloc_str, sizeof(dbgloc_str)))) { 1089 | sections->debug_loc_size = sect_hdr->sh_size; 1090 | sections->debug_loc = sections->file_buffer + sect_hdr->sh_offset; 1091 | } 1092 | } 1093 | 1094 | if (!(sections->debug_info && sections->debug_abbrev && sections->debug_line)) { 1095 | panic("TODO Currently this debugger only supports binaries with debug symbols!\n"); 1096 | } 1097 | } 1098 | 1099 | void get_dynamic_libraries(DWSections *sections) { 1100 | if (!sections->dynamic) { 1101 | panic("TODO ELF does not contain a dynamic section!\n"); 1102 | } 1103 | 1104 | for (int i = 0; i < sections->dynamic_size; i += sizeof(ELF64_Dyn)) { 1105 | ELF64_Dyn *dyn_entry = (ELF64_Dyn *)(sections->dynamic + i); 1106 | if (dyn_entry->tag == DT_NEEDED) { 1107 | printf("NEEDED %s\n", sections->dynstr + dyn_entry->val); 1108 | } 1109 | } 1110 | } 1111 | 1112 | void build_block_table(DWSections *sections, Block **ext_block_table, uint64_t *blk_len, uint64_t *block_max) { 1113 | Block *block_table = *ext_block_table; 1114 | 1115 | int abbrev_len = 0; 1116 | AbbrevUnit abbrev_entries[ABBREV_MAX] = {0}; 1117 | uint64_t cu_idx = 0; 1118 | 1119 | int i = 0; 1120 | while (i < sections->debug_abbrev_size) { 1121 | uint32_t leb_size = 0; 1122 | uint64_t abbrev_code = get_leb128_u(sections->debug_abbrev + i, &leb_size); 1123 | 1124 | i += leb_size; 1125 | if (abbrev_code == 0) { 1126 | cu_idx++; 1127 | continue; 1128 | } 1129 | 1130 | if (abbrev_len + 1 > ABBREV_MAX) { 1131 | panic("TODO 1 This should probably be dynamic!\n"); 1132 | } 1133 | 1134 | AbbrevUnit *entry = &abbrev_entries[abbrev_len++]; 1135 | entry->id = abbrev_code; 1136 | entry->cu_idx = cu_idx; 1137 | 1138 | leb_size = 0; 1139 | entry->type = get_leb128_u(sections->debug_abbrev + i, &leb_size); 1140 | i += leb_size; 1141 | 1142 | entry->has_children = sections->debug_abbrev[i]; 1143 | i += 1; 1144 | 1145 | entry->attr_buf = sections->debug_abbrev + i; 1146 | 1147 | int attr_count = 0; 1148 | while (i < sections->debug_abbrev_size) { 1149 | uint64_t entry_size = 0; 1150 | 1151 | leb_size = 0; 1152 | uint64_t attr_name = get_leb128_u(sections->debug_abbrev + i, &leb_size); 1153 | if (!leb_size) { 1154 | panic("failed to parse leb!\n"); 1155 | } 1156 | entry_size += leb_size; 1157 | 1158 | leb_size = 0; 1159 | uint64_t attr_form = get_leb128_u(sections->debug_abbrev + i + entry_size, &leb_size); 1160 | if (!leb_size) { 1161 | panic("failed to parse leb!\n"); 1162 | } 1163 | entry_size += leb_size; 1164 | 1165 | i += entry_size; 1166 | if (attr_name == 0 && attr_form == 0) { 1167 | break; 1168 | } 1169 | 1170 | attr_count += 1; 1171 | } 1172 | 1173 | entry->attr_count = attr_count; 1174 | } 1175 | 1176 | uint64_t entry_stack[BLOCK_STACK_MAX] = {0}; 1177 | uint64_t cur_cu_count = 0; 1178 | uint64_t cur_cu_idx = 0; 1179 | uint64_t cur_cu_off = 0; 1180 | 1181 | // Reserve block 0 as the full program block 1182 | block_table[0].type = DW_TAG_program; 1183 | block_table[0].name = "Program Top"; 1184 | entry_stack[0] = 0; 1185 | *blk_len += 1; 1186 | 1187 | i = 0; 1188 | while (i < sections->debug_info_size) { 1189 | DWARF32_CUHdr *cu_hdr = (DWARF32_CUHdr *)(sections->debug_info + i); 1190 | 1191 | if ((*(uint32_t *)sections->debug_info) == 0xFFFFFFFF) { 1192 | panic("TODO Currently this debugger only handles 32 bit DWARF!\n"); 1193 | } 1194 | 1195 | if (cu_hdr->unit_length == 0) { 1196 | i += sizeof(cu_hdr->unit_length); 1197 | continue; 1198 | } 1199 | 1200 | if (!(cu_hdr->version == 4 || cu_hdr->version == 3)) { 1201 | panic("TODO This code supports DWARF 3 and 4, got %d!\n", cu_hdr->version); 1202 | } 1203 | i += sizeof(DWARF32_CUHdr); 1204 | 1205 | cur_cu_off = i - sizeof(DWARF32_CUHdr); 1206 | cur_cu_idx = *blk_len; 1207 | 1208 | uint64_t child_level = 1; 1209 | do { 1210 | uint32_t leb_size = 0; 1211 | uint64_t abbrev_id = get_leb128_u(sections->debug_info + i, &leb_size); 1212 | i += leb_size; 1213 | 1214 | if (abbrev_id == 0) { 1215 | child_level--; 1216 | continue; 1217 | } 1218 | 1219 | if (*blk_len + 1 > *block_max) { 1220 | uint64_t old_max = *block_max; 1221 | uint64_t old_size = old_max * sizeof(Block); 1222 | 1223 | *ext_block_table = realloc(*ext_block_table, old_size * 2); 1224 | block_table = *ext_block_table; 1225 | 1226 | memset(block_table + old_max, 0, old_size); 1227 | *block_max = *block_max * 2; 1228 | } 1229 | 1230 | AbbrevUnit *entry = NULL; 1231 | int cur_au = 0; 1232 | for (; cur_au < abbrev_len; cur_au++) { 1233 | AbbrevUnit *tmp = &abbrev_entries[cur_au]; 1234 | if (tmp->id == abbrev_id && tmp->cu_idx == cur_cu_count) { 1235 | entry = tmp; 1236 | break; 1237 | } 1238 | } 1239 | if (!entry) { 1240 | panic("Unable to find abbrev_table entry %lu\n", abbrev_id); 1241 | } 1242 | 1243 | Block *blk = &block_table[*blk_len]; 1244 | blk->au_offset = i - 1; 1245 | blk->has_children = entry->has_children; 1246 | blk->type = entry->type; 1247 | blk->attr_buf = entry->attr_buf; 1248 | blk->attr_count = entry->attr_count; 1249 | blk->cu_idx = cur_cu_idx; 1250 | blk->depth = child_level; 1251 | 1252 | uint64_t entry_offset = 0; 1253 | for (int j = 0; j < (entry->attr_count * 2); j += 2) { 1254 | uint64_t entry_start = entry_offset; 1255 | 1256 | uint32_t leb_size = 0; 1257 | uint64_t attr_name = get_leb128_u(entry->attr_buf + entry_offset, &leb_size); 1258 | if (!leb_size) { 1259 | panic("failed to parse leb!\n"); 1260 | } 1261 | entry_offset += leb_size; 1262 | 1263 | uint64_t attr_form = get_leb128_u(entry->attr_buf + entry_offset, &leb_size); 1264 | if (!leb_size) { 1265 | panic("failed to parse leb!\n"); 1266 | } 1267 | entry_offset += leb_size; 1268 | 1269 | DWResult ret = parse_data(sections, i, entry->attr_buf + entry_start); 1270 | 1271 | switch (attr_name) { 1272 | case DW_AT_data_member_location: { 1273 | if (ret.type != DWUVal) { 1274 | panic("Unable to handle member location exprs\n"); 1275 | } 1276 | 1277 | blk->member_offset = ret.data.val; 1278 | blk->member_offset_present = true; 1279 | } break; 1280 | case DW_AT_frame_base: { 1281 | blk->frame_base_expr = ret.data.expr; 1282 | blk->frame_base_expr_len = ret.size; 1283 | } break; 1284 | case DW_AT_low_pc: { 1285 | blk->low_pc = ret.data.val; 1286 | } break; 1287 | case DW_AT_high_pc: { 1288 | blk->high_pc = ret.data.val; 1289 | } break; 1290 | case DW_AT_name: { 1291 | blk->name = ret.data.str; 1292 | } break; 1293 | case DW_AT_type: { 1294 | blk->type_offset = (uint32_t)(ret.data.val + cur_cu_off); 1295 | } break; 1296 | case DW_AT_language: { 1297 | if (!(ret.data.val == 0x0c || ret.data.val == 32769)) { 1298 | panic("Debugger currently only supports C99 and x64 ASM, got %lu!\n", ret.data.val); 1299 | } 1300 | } break; 1301 | case DW_AT_location: { 1302 | if (ret.type == DWExpr) { 1303 | blk->loc_expr = ret.data.expr; 1304 | blk->loc_expr_len = ret.size; 1305 | } else if (attr_form == DW_FORM_sec_offset) { 1306 | blk->loc_list = sections->debug_loc + ret.data.val; 1307 | } else { 1308 | panic("Unable to handle static locations!\n"); 1309 | } 1310 | } break; 1311 | case DW_AT_byte_size: { 1312 | blk->type_width = ret.data.val; 1313 | } break; 1314 | case DW_AT_encoding: { 1315 | blk->type_encoding = ret.data.val; 1316 | } break; 1317 | case DW_AT_comp_dir: { 1318 | blk->comp_dir = ret.data.str; 1319 | } break; 1320 | case DW_AT_const_value: { 1321 | } break; 1322 | default: { } 1323 | } 1324 | 1325 | i += ret.skip; 1326 | } 1327 | 1328 | entry_stack[child_level] = *blk_len; 1329 | 1330 | uint64_t parent_idx = entry_stack[child_level - 1]; 1331 | if (entry->has_children) { 1332 | child_level++; 1333 | } 1334 | 1335 | if (*blk_len != parent_idx) { 1336 | blk->parent_idx = parent_idx; 1337 | } 1338 | 1339 | *blk_len += 1; 1340 | } while (i < sections->debug_info_size && child_level > 1); 1341 | 1342 | cur_cu_count += 1; 1343 | } 1344 | 1345 | for (uint64_t i = 0; i < *blk_len; i++) { 1346 | Block *b1 = &block_table[i]; 1347 | if (!b1->type_offset) { 1348 | continue; 1349 | } 1350 | 1351 | for (uint64_t j = 0; j < *blk_len; j++) { 1352 | Block *b2 = &block_table[j]; 1353 | if (b1->type_offset == b2->au_offset) { 1354 | b1->type_idx = j; 1355 | break; 1356 | } 1357 | } 1358 | } 1359 | } 1360 | 1361 | uint64_t parse_dwarfv4_line_hdr(DWARF_LineHdr *line_hdr, dol_t *ptr) { 1362 | if (ptr->offset + sizeof(DWARF_V4_LineHdr) > ptr->length) { 1363 | panic("Not enough space for DWARF line header!\n"); 1364 | } 1365 | 1366 | DWARF_V4_LineHdr *int_line_hdr = (DWARF_V4_LineHdr *)(ptr->data + ptr->offset); 1367 | line_hdr->min_inst_length = int_line_hdr->min_inst_length; 1368 | line_hdr->max_ops_per_inst = int_line_hdr->max_ops_per_inst; 1369 | line_hdr->default_is_stmt = int_line_hdr->default_is_stmt; 1370 | line_hdr->line_base = int_line_hdr->line_base; 1371 | line_hdr->line_range = int_line_hdr->line_range; 1372 | line_hdr->opcode_base = int_line_hdr->opcode_base; 1373 | 1374 | return sizeof(DWARF_V4_LineHdr); 1375 | } 1376 | 1377 | uint64_t parse_dwarfv3_line_hdr(DWARF_LineHdr *line_hdr, dol_t *ptr) { 1378 | if (ptr->offset + sizeof(DWARF_V3_LineHdr) > ptr->length) { 1379 | panic("Not enough space for DWARF line header!\n"); 1380 | } 1381 | 1382 | DWARF_V3_LineHdr *int_line_hdr = (DWARF_V3_LineHdr *)(ptr->data + ptr->offset); 1383 | line_hdr->min_inst_length = int_line_hdr->min_inst_length; 1384 | line_hdr->default_is_stmt = int_line_hdr->default_is_stmt; 1385 | line_hdr->line_base = int_line_hdr->line_base; 1386 | line_hdr->line_range = int_line_hdr->line_range; 1387 | line_hdr->opcode_base = int_line_hdr->opcode_base; 1388 | 1389 | return sizeof(DWARF_V3_LineHdr); 1390 | } 1391 | 1392 | void build_line_tables(DWSections *sections, CULineTable **ext_line_table, uint64_t *line_tables_len, uint64_t *line_tables_max) { 1393 | CULineTable *line_tables = *ext_line_table; 1394 | 1395 | int i = 0; 1396 | while (i < sections->debug_line_size) { 1397 | uint32_t unit_length = *(uint32_t *)(sections->debug_line + i); 1398 | if (unit_length == 0xFFFFFFFF) { 1399 | panic("TODO Currently this debugger only handles 32 bit DWARF!\n"); 1400 | } 1401 | i += sizeof(unit_length); 1402 | 1403 | if (unit_length == 0) { 1404 | continue; 1405 | } 1406 | 1407 | uint16_t version = *(uint16_t *)(sections->debug_line + i); 1408 | if (!(version == 4 || version == 3)) { 1409 | panic("TODO This code supports DWARF 3 and 4, got %d!\n", version); 1410 | } 1411 | i += sizeof(version); 1412 | 1413 | // TODO make this vary to support DWARF64 1414 | uint64_t header_length = *(uint32_t *)(sections->debug_line + i); 1415 | uint32_t header_length_size = sizeof(uint32_t); 1416 | i += header_length_size; 1417 | 1418 | DWARF_LineHdr line_hdr = {0}; 1419 | dol_t ptr = { .data = sections->debug_line, .offset = i, .length = sections->debug_line_size }; 1420 | if (version == 3) { 1421 | i += parse_dwarfv3_line_hdr(&line_hdr, &ptr); 1422 | } else if (version == 4) { 1423 | i += parse_dwarfv4_line_hdr(&line_hdr, &ptr); 1424 | } 1425 | 1426 | /* 1427 | printf("min inst length: %d\n", line_hdr.min_inst_length); 1428 | printf("default is stmt: %d\n", line_hdr.default_is_stmt); 1429 | printf("line base: %d\n", line_hdr.line_base); 1430 | printf("line range: %d\n", line_hdr.line_range); 1431 | printf("opcode base: %d\n", line_hdr.opcode_base); 1432 | */ 1433 | 1434 | if (line_hdr.opcode_base != 13) { 1435 | panic("TODO This debugger can't handle non-standard line ops! %d\n", line_hdr.opcode_base); 1436 | } 1437 | 1438 | CULineTable *line_table = &line_tables[*line_tables_len]; 1439 | if (*line_tables_len + 1 > *line_tables_max) { 1440 | panic("TODO make the line tables properly stretchy!\n"); 1441 | } 1442 | *line_tables_len += 1; 1443 | 1444 | int op_lengths_size = line_hdr.opcode_base - 1; 1445 | i += op_lengths_size; 1446 | 1447 | line_table->dirs[0] = sections->cur_dir; 1448 | int dir_count = 1; 1449 | while (i < sections->debug_line_size) { 1450 | char *dirname = (char *)(sections->debug_line + i); 1451 | line_table->dirs[dir_count] = dirname; 1452 | 1453 | int dirname_length = strnlen(dirname, sections->debug_line_size - i); 1454 | i += dirname_length + 1; 1455 | if (dirname_length == 0) { 1456 | break; 1457 | } 1458 | 1459 | dir_count++; 1460 | } 1461 | 1462 | int file_count = 1; 1463 | while (i < sections->debug_line_size) { 1464 | char *filename = (char *)(sections->debug_line + i); 1465 | 1466 | int filename_length = strnlen(filename, sections->debug_line_size - i); 1467 | i += filename_length + 1; 1468 | if (filename_length == 0) { 1469 | break; 1470 | } 1471 | 1472 | uint32_t leb_size = 0; 1473 | uint64_t dir_idx = get_leb128_u(sections->debug_line + i, &leb_size); 1474 | i += leb_size; 1475 | 1476 | leb_size = 0; 1477 | get_leb128_u(sections->debug_line + i, &leb_size); // last modified 1478 | i += leb_size; 1479 | 1480 | leb_size = 0; 1481 | get_leb128_u(sections->debug_line + i, &leb_size); // file size 1482 | i += leb_size; 1483 | 1484 | line_table->filenames[file_count] = filename; 1485 | line_table->dir_idx[file_count] = dir_idx; 1486 | file_count++; 1487 | } 1488 | 1489 | uint32_t cu_size = unit_length + sizeof(unit_length); 1490 | uint32_t hdr_size = header_length + sizeof(unit_length) + sizeof(version) + header_length_size; 1491 | uint32_t rem_size = cu_size - hdr_size; 1492 | 1493 | line_table->dir_count = dir_count; 1494 | line_table->file_count = file_count; 1495 | line_table->op_buf = sections->debug_line + i; 1496 | line_table->op_buf_size = rem_size; 1497 | line_table->opcode_base = line_hdr.opcode_base; 1498 | line_table->line_base = line_hdr.line_base; 1499 | line_table->line_range = line_hdr.line_range; 1500 | 1501 | line_table->line_max = 4096; 1502 | line_table->lines = calloc(sizeof(LineMachine), line_table->line_max); 1503 | line_table->line_count = 0; 1504 | 1505 | i += rem_size; 1506 | } 1507 | 1508 | 1509 | // Process Line Ops 1510 | for (uint64_t i = 0; i < *line_tables_len; i++) { 1511 | CULineTable *line_table = &line_tables[i]; 1512 | 1513 | LineMachine lm_state; 1514 | init_line_machine(&lm_state, line_table->default_is_stmt); 1515 | 1516 | for (uint32_t j = 0; j < line_table->op_buf_size; j++) { 1517 | uint8_t op_byte = *(line_table->op_buf + j); 1518 | 1519 | if (op_byte >= line_table->opcode_base) { 1520 | uint8_t real_op = op_byte - line_table->opcode_base; 1521 | 1522 | int line_inc = line_table->line_base + (real_op % line_table->line_range); 1523 | int addr_inc = real_op / line_table->line_range; 1524 | 1525 | lm_state.line_num += line_inc; 1526 | lm_state.address += addr_inc; 1527 | 1528 | memcpy(&line_table->lines[line_table->line_count++], &lm_state, sizeof(lm_state)); 1529 | 1530 | lm_state.basic_block = false; 1531 | lm_state.prologue_end = false; 1532 | lm_state.epilogue_begin = false; 1533 | lm_state.discriminator = 0; 1534 | 1535 | continue; 1536 | } 1537 | 1538 | switch (op_byte) { 1539 | case 0: { // Extended Opcodes 1540 | 1541 | j += 2; 1542 | uint8_t real_op = *(line_table->op_buf + j); 1543 | 1544 | switch (real_op) { 1545 | case DW_LNE_end_sequence: { 1546 | lm_state.end_sequence = true; 1547 | memcpy(&line_table->lines[line_table->line_count++], &lm_state, sizeof(lm_state)); 1548 | } break; 1549 | case DW_LNE_set_address: { 1550 | uint64_t address = *(uint64_t *)(line_table->op_buf + j + 1); 1551 | 1552 | lm_state.address = address; 1553 | j += sizeof(address); 1554 | 1555 | } break; 1556 | default: { 1557 | panic("Unhandled extended lineVM op! %02x\n", real_op); 1558 | } 1559 | } 1560 | } break; 1561 | case DW_LNS_copy: { 1562 | memcpy(&line_table->lines[line_table->line_count++], &lm_state, sizeof(lm_state)); 1563 | 1564 | lm_state.discriminator = 0; 1565 | lm_state.basic_block = false; 1566 | lm_state.prologue_end = false; 1567 | lm_state.epilogue_begin = false; 1568 | } break; 1569 | case DW_LNS_advance_pc: { 1570 | uint32_t leb_size = 0; 1571 | uint64_t addr_inc = get_leb128_u(line_table->op_buf + j + 1, &leb_size); 1572 | 1573 | lm_state.address += addr_inc; 1574 | 1575 | j += leb_size; 1576 | } break; 1577 | case DW_LNS_advance_line: { 1578 | uint32_t leb_size = 0; 1579 | int64_t line_inc = get_leb128_i(line_table->op_buf + j + 1, &leb_size); 1580 | 1581 | lm_state.line_num += line_inc; 1582 | 1583 | j += leb_size; 1584 | } break; 1585 | case DW_LNS_set_file: { 1586 | uint32_t leb_size = 0; 1587 | uint64_t file_idx = get_leb128_u(line_table->op_buf + j + 1, &leb_size); 1588 | 1589 | lm_state.file_idx = file_idx; 1590 | 1591 | j += leb_size; 1592 | } break; 1593 | case DW_LNS_set_column: { 1594 | uint32_t leb_size = 0; 1595 | uint64_t col_num = get_leb128_u(line_table->op_buf + j + 1, &leb_size); 1596 | 1597 | lm_state.col_num = col_num; 1598 | 1599 | j += leb_size; 1600 | } break; 1601 | case DW_LNS_negate_stmt: { 1602 | lm_state.is_stmt = !lm_state.is_stmt; 1603 | } break; 1604 | case DW_LNS_const_add_pc: { 1605 | int addr_inc = (255 - line_table->opcode_base) / line_table->line_range; 1606 | lm_state.address += addr_inc; 1607 | } break; 1608 | case DW_LNS_set_prologue_end: { 1609 | lm_state.prologue_end = true; 1610 | } break; 1611 | default: { 1612 | panic("Unhandled lineVM op! %02x\n", op_byte); 1613 | } 1614 | } 1615 | } 1616 | } 1617 | } 1618 | 1619 | void init_debug_state(DebugState *dbg, char *bin_name, char **args) { 1620 | memset(&dbg->sections, 0, sizeof(dbg->sections)); 1621 | load_elf_sections(&dbg->sections, bin_name); 1622 | dbg->sections.args = args; 1623 | 1624 | get_dynamic_libraries(&dbg->sections); 1625 | 1626 | dbg->block_max = BLOCK_MAX; 1627 | dbg->block_table = (Block *)calloc(sizeof(Block), dbg->block_max); 1628 | dbg->block_len = 0; 1629 | build_block_table(&dbg->sections, &dbg->block_table, &dbg->block_len, &dbg->block_max); 1630 | 1631 | dbg->line_tables_max = LINE_TABLES_MAX; 1632 | dbg->line_tables = (CULineTable *)malloc(sizeof(CULineTable) * dbg->line_tables_max); 1633 | dbg->line_tables_len = 0; 1634 | build_line_tables(&dbg->sections, &dbg->line_tables, &dbg->line_tables_len, &dbg->line_tables_max); 1635 | 1636 | dbg->break_max = BREAKPOINTS_MAX; 1637 | dbg->break_table = (Breakpoint *)calloc(sizeof(Breakpoint), dbg->break_max); 1638 | dbg->break_len = 0; 1639 | 1640 | dbg->watch_max = WATCHPOINTS_MAX; 1641 | dbg->watch_table = (Watchpoint *)calloc(sizeof(Watchpoint), dbg->watch_max); 1642 | dbg->watch_len = 0; 1643 | 1644 | memset(dbg->hw_slots, 0, sizeof(dbg->hw_slots)); 1645 | dbg->hw_slots_max = HW_SLOTS_MAX; 1646 | dbg->sw_watch_enabled = false; 1647 | 1648 | dbg->reset_idx = 0; 1649 | dbg->should_reset = false; 1650 | } 1651 | 1652 | void reset_checks(DebugState *dbg) { 1653 | free(dbg->break_table); 1654 | 1655 | for (uint64_t i = 0; i < dbg->watch_len; i++) { 1656 | free(dbg->watch_table[i].old_data); 1657 | free(dbg->watch_table[i].new_data); 1658 | } 1659 | free(dbg->watch_table); 1660 | 1661 | dbg->break_max = BREAKPOINTS_MAX; 1662 | dbg->break_table = (Breakpoint *)calloc(sizeof(Breakpoint), dbg->break_max); 1663 | dbg->break_len = 0; 1664 | 1665 | dbg->watch_max = WATCHPOINTS_MAX; 1666 | dbg->watch_table = (Watchpoint *)calloc(sizeof(Watchpoint), dbg->watch_max); 1667 | dbg->watch_len = 0; 1668 | 1669 | memset(dbg->hw_slots, 0, sizeof(dbg->hw_slots)); 1670 | dbg->hw_slots_max = HW_SLOTS_MAX; 1671 | dbg->sw_watch_enabled = false; 1672 | 1673 | dbg->reset_idx = 0; 1674 | dbg->should_reset = false; 1675 | } 1676 | 1677 | void free_debug_state(DebugState *dbg) { 1678 | free(dbg->block_table); 1679 | 1680 | for (uint64_t i = 0; i < dbg->line_tables_len; i++) { 1681 | free(dbg->line_tables[i].lines); 1682 | } 1683 | free(dbg->line_tables); 1684 | free(dbg->break_table); 1685 | 1686 | for (uint64_t i = 0; i < dbg->watch_len; i++) { 1687 | free(dbg->watch_table[i].old_data); 1688 | free(dbg->watch_table[i].new_data); 1689 | } 1690 | free(dbg->watch_table); 1691 | 1692 | free(dbg->sections.bin_name); 1693 | free(dbg->sections.file_buffer); 1694 | 1695 | memset(dbg, 0, sizeof(DebugState)); 1696 | } 1697 | 1698 | uint64_t find_next_line_addr_in_file(DebugState *dbg, char *break_file, uint32_t break_line) { 1699 | uint64_t err = ~0; 1700 | 1701 | for (uint64_t i = 0; i < dbg->line_tables_len; i++) { 1702 | CULineTable *line_table = &dbg->line_tables[i]; 1703 | int j; 1704 | 1705 | uint32_t break_file_idx = 0; 1706 | for (j = 1; j < line_table->file_count; j++) { 1707 | if (!(strcmp(line_table->filenames[j], break_file))) { 1708 | break_file_idx = j; 1709 | break; 1710 | } 1711 | } 1712 | if (j == line_table->file_count) { 1713 | return err; 1714 | } 1715 | 1716 | for (int j = 0; j < line_table->line_count; j++) { 1717 | LineMachine line = line_table->lines[j]; 1718 | 1719 | if (line.line_num > break_line && break_file_idx == line.file_idx) { 1720 | printf("next line from %u is %u, address 0x%lx\n", break_line, line.line_num, line.address); 1721 | return line.address; 1722 | } 1723 | } 1724 | } 1725 | 1726 | return err; 1727 | } 1728 | 1729 | uint64_t find_line_addr_in_file(DebugState *dbg, char *break_file, uint32_t break_line) { 1730 | uint64_t break_addr = ~0; 1731 | 1732 | for (uint64_t i = 0; i < dbg->line_tables_len; i++) { 1733 | CULineTable *line_table = &dbg->line_tables[i]; 1734 | int j; 1735 | 1736 | uint32_t break_file_idx = 0; 1737 | for (j = 1; j < line_table->file_count; j++) { 1738 | if (!(strcmp(line_table->filenames[j], break_file))) { 1739 | break_file_idx = j; 1740 | break; 1741 | } 1742 | } 1743 | if (j == line_table->file_count) { 1744 | continue; 1745 | } 1746 | 1747 | for (int j = 0; j < line_table->line_count; j++) { 1748 | LineMachine line = line_table->lines[j]; 1749 | 1750 | if (line.line_num == break_line && break_file_idx == line.file_idx) { 1751 | break_addr = line.address; 1752 | break; 1753 | } 1754 | } 1755 | } 1756 | if (break_addr == (uint64_t)~0) { 1757 | panic("Failed to find the address for line %d\n", break_line); 1758 | } 1759 | 1760 | return break_addr; 1761 | } 1762 | 1763 | typedef struct { 1764 | uint64_t line; 1765 | uint64_t address; 1766 | char *file; 1767 | } FileLine; 1768 | 1769 | int get_approx_line_for_addr(DebugState *dbg, uint64_t addr, FileLine *fl) { 1770 | for (uint64_t i = 0; i < dbg->line_tables_len; i++) { 1771 | CULineTable *line_table = &dbg->line_tables[i]; 1772 | 1773 | for (int j = 0; j < line_table->line_count; j++) { 1774 | LineMachine *line = &line_table->lines[j]; 1775 | 1776 | if (line->address >= addr) { 1777 | fl->line = line->line_num; 1778 | fl->address = line->address; 1779 | fl->file = line_table->filenames[line->file_idx]; 1780 | return 1; 1781 | } 1782 | } 1783 | } 1784 | 1785 | return 0; 1786 | } 1787 | 1788 | FuncFrame *find_function_frame(Block *block_table, uint64_t block_len, char *func_name, FuncFrame *ff) { 1789 | for (uint64_t i = 0; i < block_len; i++) { 1790 | Block *block = &block_table[i]; 1791 | if (block->type == DW_TAG_subprogram && block->name && strcmp(block->name, func_name) == 0) { 1792 | ff->start = block->low_pc; 1793 | ff->end = block->low_pc + block->high_pc; 1794 | return ff; 1795 | } 1796 | } 1797 | 1798 | return NULL; 1799 | } 1800 | 1801 | FuncFrame *find_function_frame_approx(DebugState *dbg, char *func_name, FuncFrame *ff) { 1802 | if (find_function_frame(dbg->block_table, dbg->block_len, func_name, ff)) { 1803 | ff->start += 4; 1804 | 1805 | FileLine fl = {0}; 1806 | if (!get_approx_line_for_addr(dbg, ff->start, &fl)) { 1807 | panic("failed to get line for address!\n"); 1808 | } 1809 | 1810 | if (ff->end > fl.address) { 1811 | ff->start = fl.address; 1812 | } 1813 | 1814 | ff->end -= 1; 1815 | return ff; 1816 | } 1817 | 1818 | return NULL; 1819 | } 1820 | 1821 | CurrentScopes *get_scopes(DebugState *dbg, uint64_t addr, CurrentScopes *sp) { 1822 | uint64_t blk_idx = dbg->block_len - 1; 1823 | Block *scope_block = NULL; 1824 | for (; blk_idx > 0; blk_idx--) { 1825 | Block *blk = &dbg->block_table[blk_idx]; 1826 | if ((addr >= blk->low_pc) && (addr <= (blk->low_pc + blk->high_pc))) { 1827 | scope_block = blk; 1828 | break; 1829 | } 1830 | } 1831 | if (scope_block == NULL) { 1832 | return NULL; 1833 | } 1834 | 1835 | uint64_t scope_idx = blk_idx; 1836 | uint64_t framed_scope_idx = 0; 1837 | 1838 | // Find nearest parent scope with frame_base_expr 1839 | Block *framed_scope = NULL; 1840 | if (scope_block->frame_base_expr) { 1841 | framed_scope = scope_block; 1842 | framed_scope_idx = scope_idx; 1843 | } else { 1844 | Block *tmp_scope_block = scope_block; 1845 | uint64_t tmp_scope_block_idx = blk_idx; 1846 | for (;;) { 1847 | if (tmp_scope_block->parent_idx == tmp_scope_block_idx) { 1848 | panic("parent == child, hotlooping\n"); 1849 | } 1850 | 1851 | if (tmp_scope_block->frame_base_expr) { 1852 | framed_scope = tmp_scope_block; 1853 | framed_scope_idx = tmp_scope_block_idx; 1854 | break; 1855 | } 1856 | 1857 | tmp_scope_block_idx = tmp_scope_block->parent_idx; 1858 | tmp_scope_block = &dbg->block_table[tmp_scope_block_idx]; 1859 | } 1860 | } 1861 | 1862 | sp->scope_idx = scope_idx; 1863 | sp->framed_scope_idx = framed_scope_idx; 1864 | 1865 | return sp; 1866 | } 1867 | 1868 | void eval_expr(struct user_regs_struct *regs, DWExprMachine *em, DWExpression in_ex, int depth) { 1869 | for (uint64_t i = 0; i < in_ex.len; i++) { 1870 | uint8_t op = in_ex.expr[i]; 1871 | 1872 | if (op >= 0x50 && op <= 0x6f) { 1873 | em->val_stack[em->val_stack_len++] = get_regval_for_dwarf_reg(regs, op - 0x50); 1874 | } else { 1875 | switch (op) { 1876 | case DW_OP_fbreg: { 1877 | uint32_t leb_size = 0; 1878 | int64_t data = get_leb128_i(in_ex.expr + i + 1, &leb_size); 1879 | 1880 | DWExpression frame_holder_ex = { em->frame_holder->frame_base_expr, em->frame_holder->frame_base_expr_len }; 1881 | eval_expr(regs, em, frame_holder_ex, depth + 1); 1882 | 1883 | em->val_stack_len--; 1884 | em->val_stack[em->val_stack_len] = ((int64_t)em->val_stack[em->val_stack_len]) + data; 1885 | 1886 | i += leb_size; 1887 | } break; 1888 | default: { 1889 | panic("\nUnhandled op 0x%x, %s!\n", op, dwarf_expr_op_to_str(op)); 1890 | } 1891 | } 1892 | } 1893 | } 1894 | } 1895 | 1896 | // Intel Vol. 3B 17-5 - PG 3433-3434 -- covers debug register operation 1897 | void update_hw_breaks(DebugState *dbg, int pid, Block *framed_scope) { 1898 | 1899 | struct user_regs_struct regs; 1900 | ptrace(PTRACE_GETREGS, pid, NULL, ®s); 1901 | 1902 | if (dbg->watch_len > HW_SLOTS_MAX) { 1903 | panic("TODO Can't handle software watchpoints yet!\n"); 1904 | } 1905 | 1906 | memset(&dbg->hw_slots, 0, sizeof(dbg->hw_slots)); 1907 | int hw_slots_len = 0; 1908 | int hw_slots_used = 0; 1909 | int slot_off = 0; 1910 | 1911 | uint64_t dr7 = 0b00000000000000010000000100000000; 1912 | for (uint64_t i = 0; i < dbg->watch_len; i++) { 1913 | Watchpoint *wp = &dbg->watch_table[i]; 1914 | Block *var = &dbg->block_table[wp->var_idx]; 1915 | 1916 | char width_bits = 0; 1917 | int watchpoints_used = 5; 1918 | if (wp->watch_width == 8) { width_bits = 0b10; watchpoints_used = 1; } 1919 | else if (wp->watch_width == 4) { width_bits = 0b11; watchpoints_used = 1; } 1920 | else if (wp->watch_width == 2) { width_bits = 0b01; watchpoints_used = 1; } 1921 | else if (wp->watch_width == 1) { width_bits = 0b00; watchpoints_used = 1; } 1922 | else if (wp->watch_width > 8) { 1923 | width_bits = 0b10; 1924 | 1925 | int rem = !!(wp->watch_width % 8); 1926 | watchpoints_used = (wp->watch_width / 8) + rem; 1927 | } else { 1928 | panic("unable to handle unaligned variables!\n"); 1929 | } 1930 | 1931 | if (hw_slots_used + watchpoints_used > HW_SLOTS_MAX) { 1932 | panic("TODO Needs software watchpoint support!\n"); 1933 | } 1934 | 1935 | // Get variable address 1936 | DWExprMachine em = {0}; 1937 | em.frame_holder = framed_scope; 1938 | DWExpression ex = { .expr = var->loc_expr, .len = var->loc_expr_len }; 1939 | eval_expr(®s, &em, ex, 0); 1940 | 1941 | uint64_t var_addr = em.val_stack[0]; 1942 | 1943 | if (wp->member_idx) { 1944 | var_addr += wp->member_offset; 1945 | } 1946 | 1947 | int is_8b_aligned = (var_addr & 0x7) == 0; 1948 | int is_4b_aligned = (var_addr & 0x3) == 0; 1949 | int is_2b_aligned = (var_addr & 0x1) == 0; 1950 | 1951 | //printf("8 %d, 4 %d, 2 %d\n", is_8b_aligned, is_4b_aligned, is_2b_aligned); 1952 | if (!(is_8b_aligned || is_4b_aligned || is_2b_aligned || wp->watch_width == 1)) { 1953 | panic("Unable to handle unaligned watchpoints!\n"); 1954 | } 1955 | 1956 | HWBreak *br = &dbg->hw_slots[hw_slots_len]; 1957 | br->type = HWWatchpoint; 1958 | br->idx = i; 1959 | hw_slots_len++; 1960 | hw_slots_used += watchpoints_used; 1961 | 1962 | memset(wp->old_data, 0, wp->watch_width); 1963 | 1964 | uint64_t rem_width = wp->watch_width; 1965 | uint64_t leftovers; 1966 | for (int j = 0; j < watchpoints_used; j++) { 1967 | if (rem_width > 8) { 1968 | leftovers = 8; 1969 | rem_width -= 8; 1970 | } else { 1971 | leftovers = rem_width; 1972 | rem_width = 0; 1973 | } 1974 | 1975 | uint64_t cur_val = ptrace(PTRACE_PEEKDATA, pid, (void *)(var_addr + (j * 8)), NULL); 1976 | memcpy(wp->old_data + (j * 8), &cur_val, leftovers); 1977 | 1978 | int width_off = 18 + (4 * slot_off); 1979 | int watchtype_off = 16 + (4 * slot_off); 1980 | int register_enable_off = 2 * slot_off; 1981 | int reg_idx = slot_off; 1982 | 1983 | printf("[0x%llx] adding %s with width %lu and offset %lx to watchlist\n", regs.rip, var->name, wp->watch_width, wp->member_offset); 1984 | printf("%u, %u, %u, %u, %u\n", width_off, watchtype_off, register_enable_off, reg_idx, width_bits); 1985 | 1986 | // set: width | watch type (write only) | # register enable 1987 | dr7 |= (width_bits << width_off) | (0b01 << watchtype_off) | (0b1 << register_enable_off); 1988 | ptrace(PTRACE_POKEUSER, pid, (void *)offsetof(struct user, u_debugreg[reg_idx]), (void *)(var_addr + (j * 8))); 1989 | 1990 | slot_off++; 1991 | } 1992 | } 1993 | 1994 | printf("Setting dr7 to 0x%lx -- %lu watchpoints\n", dr7, dbg->watch_len); 1995 | if (ptrace(PTRACE_POKEUSER, pid, (void *)offsetof(struct user, u_debugreg[7]), (void *)dr7) == -1) { 1996 | panic("Failed to set dr7 correctly!\n"); 1997 | } 1998 | if (ptrace(PTRACE_POKEUSER, pid, (void *)offsetof(struct user, u_debugreg[6]), NULL)) { 1999 | panic("failed to zero dr6!\n"); 2000 | } 2001 | } 2002 | 2003 | void inject_breakpoint_at_addr(int pid, Breakpoint *br, uint64_t addr) { 2004 | uint64_t orig_data = (uint64_t)ptrace(PTRACE_PEEKDATA, pid, (void *)addr, NULL); 2005 | 2006 | // Replace first byte of code at address with int 3 2007 | uint64_t data_with_trap = (orig_data & (~0xFF)) | 0xCC; 2008 | 2009 | ptrace(PTRACE_POKEDATA, pid, (void *)addr, (void *)data_with_trap); 2010 | 2011 | br->address = addr; 2012 | br->orig_data = orig_data; 2013 | } 2014 | 2015 | void add_breakpoint(int pid, Breakpoint *br, uint64_t addr, bool permanent) { 2016 | if (br->ref_count > 0) { 2017 | printf("Doing a ref skip!\n"); 2018 | goto ref_end; 2019 | } 2020 | 2021 | inject_breakpoint_at_addr(pid, br, addr); 2022 | 2023 | ref_end: 2024 | if (permanent) { 2025 | br->ref_count += 1; 2026 | } 2027 | } 2028 | 2029 | void reset_breakpoint(DebugState *dbg, int pid) { 2030 | ptrace(PTRACE_SINGLESTEP, pid, NULL, NULL); 2031 | 2032 | int status; 2033 | waitpid(pid, &status, 0); 2034 | if (!WIFSTOPPED(status)) { 2035 | panic("Failure to break on watchpoint\n"); 2036 | } 2037 | 2038 | Breakpoint *br = &dbg->break_table[dbg->reset_idx]; 2039 | 2040 | inject_breakpoint_at_addr(pid, br, br->address); 2041 | 2042 | dbg->reset_idx = 0; 2043 | dbg->should_reset = false; 2044 | } 2045 | 2046 | void cleanup_watchpoints(DebugState *dbg, int pid, uint64_t break_addr) { 2047 | if (!dbg->watch_len) { 2048 | return; 2049 | } 2050 | 2051 | uint64_t break_idx = (uint64_t)~0; 2052 | for (uint64_t i = 0; i < dbg->break_len; i++) { 2053 | Breakpoint *br = &dbg->break_table[i]; 2054 | if (br->address == break_addr) { 2055 | break_idx = i; 2056 | break; 2057 | } 2058 | } 2059 | if (break_idx == (uint64_t)~0) { 2060 | return; 2061 | } 2062 | 2063 | // Get all watchpoints associated with breakpoint, clean up 2064 | Breakpoint *br = &dbg->break_table[break_idx]; 2065 | for (uint64_t j = dbg->watch_len - 1; j > 0; j--) { 2066 | Watchpoint *wp = &dbg->watch_table[j]; 2067 | if (wp->break_idx == break_idx) { 2068 | Watchpoint *end = &dbg->watch_table[dbg->watch_len]; 2069 | Watchpoint *old = &dbg->watch_table[j]; 2070 | 2071 | free(old->old_data); 2072 | free(old->new_data); 2073 | memcpy(old, end, sizeof(Watchpoint)); 2074 | memset(end, 0, sizeof(Watchpoint)); 2075 | dbg->watch_len--; 2076 | 2077 | br->ref_count -= 1; 2078 | } 2079 | } 2080 | 2081 | CurrentScopes scopes = {0}; 2082 | if (!get_scopes(dbg, break_addr, &scopes)) { 2083 | printf("Failed to find scope!\n"); 2084 | return; 2085 | } 2086 | 2087 | update_hw_breaks(dbg, pid, &dbg->block_table[scopes.framed_scope_idx]); 2088 | } 2089 | 2090 | 2091 | VarInfo get_var_for_name(DebugState *dbg, CurrentScopes *scopes, char *var_name) { 2092 | uint64_t var_name_len = strlen(var_name); 2093 | char *_varstr = (char *)malloc(var_name_len + 1); 2094 | strcpy(_varstr, var_name); 2095 | 2096 | char *members[MEMBERS_MAX] = {0}; 2097 | uint64_t members_len = 0; 2098 | 2099 | VarInfo info = {0}; 2100 | info.var_idx = (uint64_t)~0; 2101 | 2102 | char *ptr = _varstr; 2103 | char *head = ptr; 2104 | while (*ptr) { 2105 | if (*ptr == '.') { 2106 | *ptr = 0; 2107 | 2108 | if (members_len + 1 > MEMBERS_MAX) { 2109 | panic("No more room for more members!\n"); 2110 | } 2111 | 2112 | members[members_len++] = head; 2113 | head = ptr + 1; 2114 | } 2115 | 2116 | ptr++; 2117 | } 2118 | if (head != ptr) { 2119 | if (members_len + 1 > MEMBERS_MAX) { 2120 | panic("No more room for more members!\n"); 2121 | } 2122 | members[members_len++] = head; 2123 | } 2124 | if (!members_len) { 2125 | goto find_var_end; 2126 | } 2127 | 2128 | char *_var_name = members[0]; 2129 | uint64_t blk_idx = scopes->scope_idx; 2130 | 2131 | // Find variable in scope by descending tree from scope_block (can pass into other scopes) 2132 | Block *var_block = NULL; 2133 | for (; blk_idx < dbg->block_len; blk_idx++) { 2134 | Block *blk = &dbg->block_table[blk_idx]; 2135 | if (blk->name && strcmp(blk->name, _var_name) == 0 && (blk->type == DW_TAG_variable || blk->type == DW_TAG_formal_parameter)) { 2136 | var_block = blk; 2137 | break; 2138 | } 2139 | } 2140 | 2141 | // Try using the framed scope idx instead if we couldn't find it in the lexical scope 2142 | if (var_block == NULL) { 2143 | blk_idx = scopes->framed_scope_idx; 2144 | for (; blk_idx < dbg->block_len; blk_idx++) { 2145 | Block *blk = &dbg->block_table[blk_idx]; 2146 | if (blk->name && strcmp(blk->name, _var_name) == 0 && blk->type == DW_TAG_variable) { 2147 | var_block = blk; 2148 | break; 2149 | } 2150 | } 2151 | if (var_block == NULL) { 2152 | printf("Unable to find variable %s in scope!\n", _var_name); 2153 | goto find_var_end; 2154 | } 2155 | } 2156 | 2157 | uint64_t var_idx = blk_idx; 2158 | 2159 | // Walk parent_idx to confirm that variable obtained in last pass belongs to current scope 2160 | Block *tmp_var_block = var_block; 2161 | uint64_t tmp_var_block_idx = var_idx; 2162 | for (;;) { 2163 | if (scopes->scope_idx == tmp_var_block_idx || scopes->framed_scope_idx == tmp_var_block_idx) { 2164 | break; 2165 | } 2166 | 2167 | if (!tmp_var_block->parent_idx) { 2168 | panic("hit scope max, %s\n", tmp_var_block->name); 2169 | } 2170 | 2171 | if (tmp_var_block->parent_idx == tmp_var_block_idx) { 2172 | panic("Invalid parent, hotlooping\n"); 2173 | } 2174 | 2175 | tmp_var_block_idx = tmp_var_block->parent_idx; 2176 | if (!tmp_var_block_idx) { 2177 | panic("Unable to lookup parent of entry!\n"); 2178 | } 2179 | 2180 | tmp_var_block = &dbg->block_table[tmp_var_block_idx]; 2181 | } 2182 | 2183 | info.var_idx = var_idx; 2184 | 2185 | if (members_len == 1) { 2186 | // Resolve type for variable to get width 2187 | Block *tmp_block = var_block; 2188 | for (;;) { 2189 | if (!tmp_block->type_idx) { 2190 | break; 2191 | } 2192 | 2193 | tmp_block = &dbg->block_table[tmp_block->type_idx]; 2194 | } 2195 | 2196 | info.width = tmp_block->type_width; 2197 | goto find_var_end; 2198 | } 2199 | 2200 | 2201 | /* 2202 | * Resolve struct members 2203 | * find each member, confirm they live as part of the variables struct 2204 | * get member offset, add to running offset tally 2205 | * 2206 | */ 2207 | 2208 | Block *struct_block = var_block; 2209 | uint64_t struct_idx = 0; 2210 | for (;;) { 2211 | if (!struct_block->type_idx) { 2212 | break; 2213 | } 2214 | 2215 | struct_idx = struct_block->type_idx; 2216 | struct_block = &dbg->block_table[struct_block->type_idx]; 2217 | } 2218 | 2219 | uint64_t var_offset = 0; 2220 | uint64_t var_width = 0; 2221 | uint64_t member_idx = 0; 2222 | for (uint64_t i = 1; i < members_len; i++) { 2223 | char *sub_name = members[i]; 2224 | 2225 | for (uint64_t j = struct_idx + 1; j < dbg->block_len; j++) { 2226 | Block *member_block = &dbg->block_table[j]; 2227 | if (!strcmp(member_block->name, sub_name)) { 2228 | member_idx = j; 2229 | break; 2230 | } 2231 | 2232 | if (member_block->parent_idx != struct_idx) { 2233 | printf("Couldn't find member %s for struct!\n", sub_name); 2234 | goto find_var_end; 2235 | } 2236 | } 2237 | if (!member_idx) { 2238 | printf("Couldn't find member %s for struct!\n", sub_name); 2239 | goto find_var_end; 2240 | } 2241 | 2242 | Block *member_block = &dbg->block_table[member_idx]; 2243 | 2244 | Block *substruct_block = member_block; 2245 | uint64_t substruct_idx = 0; 2246 | for (;;) { 2247 | if (!substruct_block->type_idx) { 2248 | break; 2249 | } 2250 | 2251 | substruct_idx = substruct_block->type_idx; 2252 | substruct_block = &dbg->block_table[substruct_block->type_idx]; 2253 | } 2254 | 2255 | struct_block = substruct_block; 2256 | struct_idx = substruct_idx; 2257 | var_width = substruct_block->type_width; 2258 | 2259 | var_offset += member_block->member_offset; 2260 | } 2261 | 2262 | info.member_idx = member_idx; 2263 | info.member_offset = var_offset; 2264 | info.width = var_width; 2265 | 2266 | find_var_end: 2267 | free(_varstr); 2268 | return info; 2269 | } 2270 | 2271 | void add_watchpoint(DebugState *dbg, int pid, char *var_name) { 2272 | struct user_regs_struct regs; 2273 | ptrace(PTRACE_GETREGS, pid, NULL, ®s); 2274 | 2275 | CurrentScopes scopes = {0}; 2276 | if (!get_scopes(dbg, regs.rip, &scopes)) { 2277 | printf("Failed to find scope!\n"); 2278 | return; 2279 | } 2280 | 2281 | VarInfo info = get_var_for_name(dbg, &scopes, var_name); 2282 | if (info.var_idx == (uint64_t)~0) { 2283 | return; 2284 | } 2285 | 2286 | Block *framed_scope = &dbg->block_table[scopes.framed_scope_idx]; 2287 | Block *scope_block = &dbg->block_table[scopes.scope_idx]; 2288 | 2289 | Watchpoint *var_watch = NULL; 2290 | uint64_t watch_idx = 0; 2291 | for (uint64_t i = 0; i < dbg->watch_len; i++) { 2292 | Watchpoint *wp = &dbg->watch_table[i]; 2293 | if (wp->var_idx == info.var_idx) { 2294 | var_watch = wp; 2295 | watch_idx = i; 2296 | break; 2297 | } 2298 | } 2299 | if (var_watch == NULL) { 2300 | // If this is a function, add buffer zone for the pre/postambles 2301 | uint64_t end; 2302 | if (scopes.framed_scope_idx == scopes.scope_idx) { 2303 | FuncFrame ff = {0}; 2304 | if (!find_function_frame_approx(dbg, framed_scope->name, &ff)) { 2305 | panic("Failed to find function frame for function\n"); 2306 | } 2307 | 2308 | end = ff.end; 2309 | } else { 2310 | end = scope_block->low_pc + scope_block->high_pc; 2311 | } 2312 | 2313 | Watchpoint *wp = &dbg->watch_table[dbg->watch_len]; 2314 | wp->end = end; 2315 | wp->var_idx = info.var_idx; 2316 | wp->member_idx = info.member_idx; 2317 | wp->member_offset = info.member_offset; 2318 | watch_idx = dbg->watch_len; 2319 | 2320 | // establish a breakpoint so we can clean up the watchpoint when it drops out of scope 2321 | wp->break_idx = (uint64_t)~0; 2322 | for (uint64_t i = 0; i < dbg->break_len; i++) { 2323 | Breakpoint *br = &dbg->break_table[i]; 2324 | if (br->address == wp->end) { 2325 | wp->break_idx = i; 2326 | br->ref_count += 1; 2327 | break; 2328 | } 2329 | } 2330 | if (wp->break_idx == (uint64_t)~0) { 2331 | if (dbg->break_len + 1 > dbg->break_max) { 2332 | panic("TODO 3 This should probably be dynamic!\n"); 2333 | } 2334 | 2335 | Breakpoint *br = &dbg->break_table[dbg->break_len]; 2336 | printf("Breakpoint added @ 0x%lx\n", wp->end); 2337 | add_breakpoint(pid, br, wp->end, true); 2338 | dbg->break_len++; 2339 | } 2340 | 2341 | 2342 | wp->watch_width = info.width; 2343 | wp->old_data = (uint8_t *)calloc(wp->watch_width, sizeof(uint8_t)); 2344 | wp->new_data = (uint8_t *)calloc(wp->watch_width, sizeof(uint8_t)); 2345 | 2346 | dbg->watch_len++; 2347 | var_watch = wp; 2348 | } 2349 | 2350 | update_hw_breaks(dbg, pid, framed_scope); 2351 | } 2352 | 2353 | void continue_to_next(DebugState *dbg, int pid, bool single_step) { 2354 | struct user_regs_struct regs; 2355 | 2356 | if (dbg->should_reset) { 2357 | printf("Resetting breakpoint?\n"); 2358 | reset_breakpoint(dbg, pid); 2359 | } 2360 | 2361 | if (single_step) { 2362 | ptrace(PTRACE_SINGLESTEP, pid, NULL, NULL); 2363 | } else { 2364 | ptrace(PTRACE_CONT, pid, NULL, NULL); 2365 | } 2366 | 2367 | int status; 2368 | waitpid(pid, &status, 0); 2369 | if (!WIFSTOPPED(status)) { 2370 | int ret_val = WEXITSTATUS(status); 2371 | printf("Program died with return code %d, bye!\n", ret_val); 2372 | dbg->needs_restart = true; 2373 | return; 2374 | } 2375 | 2376 | uint64_t dr6 = ptrace(PTRACE_PEEKUSER, pid, (void *)offsetof(struct user, u_debugreg[6]), NULL); 2377 | 2378 | uint32_t triggerbits = dr6 & 0b1111; 2379 | /* 2380 | int dr7_set = (dr6 >> 12) & 1; 2381 | int single_stepped = (dr6 >> 14) & 1; 2382 | */ 2383 | 2384 | 2385 | if (ptrace(PTRACE_POKEUSER, pid, (void *)offsetof(struct user, u_debugreg[6]), NULL)) { 2386 | panic("failed to set up dr6!\n"); 2387 | } 2388 | 2389 | ptrace(PTRACE_GETREGS, pid, NULL, ®s); 2390 | FileLine fl = {0}; 2391 | if (!get_approx_line_for_addr(dbg, regs.rip - 1, &fl)) { 2392 | printf("Stopped @ 0x%llx\n", regs.rip); 2393 | } else { 2394 | printf("Stopped @ 0x%llx | line %lu in file %s\n", regs.rip, fl.line, fl.file); 2395 | } 2396 | 2397 | if (triggerbits) { 2398 | CurrentScopes scopes = {0}; 2399 | if (!get_scopes(dbg, regs.rip, &scopes)) { 2400 | return; 2401 | } 2402 | 2403 | for (uint64_t i = 0; i < dbg->hw_slots_max; i++) { 2404 | HWBreak *br = &dbg->hw_slots[i]; 2405 | if (br->type != HWWatchpoint) { 2406 | triggerbits = triggerbits >> 1; 2407 | continue; 2408 | } 2409 | 2410 | Watchpoint *wp = &dbg->watch_table[br->idx]; 2411 | Block *var = &dbg->block_table[wp->var_idx]; 2412 | 2413 | int watchpoints_used = 5; 2414 | if (wp->watch_width <= 8) { 2415 | watchpoints_used = 1; 2416 | } else if (wp->watch_width > 8) { 2417 | int rem = !!(wp->watch_width % 8); 2418 | watchpoints_used = (wp->watch_width / 8) + rem; 2419 | } 2420 | assert(watchpoints_used > 4, "unable to handle large watchpoints\n"); 2421 | 2422 | uint8_t bitmasks[HW_SLOTS_MAX] = { 0b0001, 0b0011, 0b0111, 0b1111 }; 2423 | printf("triggerbits: %u; bits: %u; watchpoints: %u\n", triggerbits, triggerbits & bitmasks[watchpoints_used - 1], watchpoints_used); 2424 | if (!(triggerbits & bitmasks[watchpoints_used - 1])) { 2425 | printf("Skipping thing here!\n"); 2426 | triggerbits = triggerbits >> watchpoints_used; 2427 | continue; 2428 | } 2429 | triggerbits = triggerbits >> watchpoints_used; 2430 | 2431 | // Find variable address 2432 | DWExprMachine em = {0}; 2433 | em.frame_holder = &dbg->block_table[scopes.framed_scope_idx]; 2434 | DWExpression ex = { .expr = var->loc_expr, .len = var->loc_expr_len }; 2435 | eval_expr(®s, &em, ex, 0); 2436 | uint64_t var_addr = em.val_stack[0]; 2437 | 2438 | if (wp->member_idx) { 2439 | var_addr += wp->member_offset; 2440 | } 2441 | 2442 | int is_8b_aligned = (var_addr & 0x7) == 0; 2443 | int is_4b_aligned = (var_addr & 0x3) == 0; 2444 | int is_2b_aligned = (var_addr & 0x1) == 0; 2445 | 2446 | // printf("8 %d, 4 %d, 2 %d\n", is_8b_aligned, is_4b_aligned, is_2b_aligned); 2447 | if (!(is_8b_aligned || is_4b_aligned || is_2b_aligned || wp->watch_width == 1)) { 2448 | panic("Unable to handle unaligned watchpoints!\n"); 2449 | } 2450 | 2451 | memset(wp->new_data, 0, wp->watch_width); 2452 | uint64_t rem_width = wp->watch_width; 2453 | uint64_t leftovers; 2454 | for (int j = 0; j < watchpoints_used; j++) { 2455 | if (rem_width > 8) { 2456 | leftovers = 8; 2457 | rem_width -= 8; 2458 | } else { 2459 | leftovers = rem_width; 2460 | rem_width = 0; 2461 | } 2462 | 2463 | uint64_t cur_val = ptrace(PTRACE_PEEKDATA, pid, (void *)(var_addr + (j * 8)), NULL); 2464 | memcpy(wp->new_data + (j * 8), &cur_val, leftovers); 2465 | } 2466 | 2467 | if (wp->watch_width <= 8) { 2468 | uint64_t old_data = 0; 2469 | uint64_t new_data = 0; 2470 | memcpy(&old_data, wp->old_data, wp->watch_width); 2471 | memcpy(&new_data, wp->new_data, wp->watch_width); 2472 | printf("[0x%llx] Variable %s changed from %lu to %lu | %lu\n", regs.rip, var->name, old_data, new_data, wp->watch_width); 2473 | } else { 2474 | printf("[0x%llx] Variable %s changed from %u to %u | %lu\n", regs.rip, var->name, wp->old_data[0], wp->new_data[0], wp->watch_width); 2475 | } 2476 | memcpy(wp->old_data, wp->new_data, wp->watch_width); 2477 | } 2478 | } 2479 | 2480 | uint64_t prev_address = regs.rip - 1; 2481 | uint64_t prev_inst = ptrace(PTRACE_PEEKDATA, pid, (void *)prev_address, NULL); 2482 | 2483 | // check if we just stepped over a trap that needs to be patched 2484 | if ((prev_inst & 0xFF) != 0xCC) { 2485 | return; 2486 | } 2487 | 2488 | cleanup_watchpoints(dbg, pid, prev_address); 2489 | 2490 | // There should only ever be 1 breakpoint per address 2491 | // (prevents replacing good orig_data with a trap unintentionally) 2492 | Breakpoint *break_check = NULL; 2493 | uint64_t i = 0; 2494 | for (; i < dbg->break_len; i++) { 2495 | Breakpoint *br = &dbg->break_table[i]; 2496 | 2497 | if (prev_address == br->address) { 2498 | break_check = br; 2499 | break; 2500 | } 2501 | } 2502 | if (!break_check) { 2503 | panic("Couldn't find breakpoint for 0x%lx?\n", prev_address); 2504 | } 2505 | 2506 | ptrace(PTRACE_POKEDATA, pid, (void *)break_check->address, (void *)break_check->orig_data); 2507 | regs.rip -= 1; 2508 | ptrace(PTRACE_SETREGS, pid, NULL, ®s); 2509 | 2510 | if (break_check->ref_count > 0) { 2511 | dbg->reset_idx = i; 2512 | dbg->should_reset = true; 2513 | } 2514 | 2515 | // Garbage collect breakpoints if they exist. This *shouldn't* cause watch/break indexing issues, 2516 | // because the watchpoints referring to the now bad breakpoint idx *should* already be gone 2517 | if (!dbg->break_len) { 2518 | return; 2519 | } 2520 | 2521 | for (uint64_t i = dbg->break_len - 1; i > 0; i--) { 2522 | Breakpoint *br = &dbg->break_table[i]; 2523 | if (br->ref_count == 0) { 2524 | Breakpoint *end = &dbg->break_table[dbg->break_len]; 2525 | Breakpoint *old = &dbg->break_table[i]; 2526 | 2527 | memcpy(old, end, sizeof(Breakpoint)); 2528 | memset(end, 0, sizeof(Breakpoint)); 2529 | dbg->break_len--; 2530 | } 2531 | } 2532 | 2533 | for (uint64_t i = 0; i < dbg->break_len; i++) { 2534 | Breakpoint *br = &dbg->break_table[i]; 2535 | if (br->address == prev_address && br->ref_count > 0) { 2536 | dbg->reset_idx = i; 2537 | break; 2538 | } 2539 | } 2540 | } 2541 | 2542 | void gather_registers(int pid, dol_t *p) { 2543 | struct user_regs_struct regs; 2544 | ptrace(PTRACE_GETREGS, pid, NULL, ®s); 2545 | 2546 | p->offset += sprintf((char *)p->data + p->offset, "RIP: 0x%llx\n", regs.rip); 2547 | p->offset += sprintf((char *)p->data + p->offset, "RAX: 0x%llx\n", regs.rax); 2548 | p->offset += sprintf((char *)p->data + p->offset, "RBX: 0x%llx\n", regs.rbx); 2549 | p->offset += sprintf((char *)p->data + p->offset, "RCX: 0x%llx\n", regs.rcx); 2550 | p->offset += sprintf((char *)p->data + p->offset, "RDX: 0x%llx\n", regs.rdx); 2551 | p->offset += sprintf((char *)p->data + p->offset, "RDI: 0x%llx\n", regs.rdi); 2552 | p->offset += sprintf((char *)p->data + p->offset, "RSI: 0x%llx\n", regs.rsi); 2553 | p->offset += sprintf((char *)p->data + p->offset, "RSP: 0x%llx\n", regs.rsp); 2554 | p->offset += sprintf((char *)p->data + p->offset, "RBP: 0x%llx\n", regs.rbp); 2555 | p->offset += sprintf((char *)p->data + p->offset, "R8: 0x%llx\n", regs.r8); 2556 | p->offset += sprintf((char *)p->data + p->offset, "R9: 0x%llx\n", regs.r9); 2557 | p->offset += sprintf((char *)p->data + p->offset, "R10: 0x%llx\n", regs.r10); 2558 | p->offset += sprintf((char *)p->data + p->offset, "R11: 0x%llx\n", regs.r11); 2559 | p->offset += sprintf((char *)p->data + p->offset, "R12: 0x%llx\n", regs.r12); 2560 | p->offset += sprintf((char *)p->data + p->offset, "R13: 0x%llx\n", regs.r13); 2561 | p->offset += sprintf((char *)p->data + p->offset, "R14: 0x%llx\n", regs.r14); 2562 | p->offset += sprintf((char *)p->data + p->offset, "R15: 0x%llx\n", regs.r15); 2563 | } 2564 | 2565 | void goto_next_flowline(DebugState *dbg, int pid) { 2566 | struct user_regs_struct regs; 2567 | ptrace(PTRACE_GETREGS, pid, NULL, ®s); 2568 | 2569 | FileLine fl = {0}; 2570 | if (!get_approx_line_for_addr(dbg, regs.rip, &fl)) { 2571 | panic("failed to get line for rip!\n"); 2572 | } 2573 | 2574 | uint64_t break_addr = find_next_line_addr_in_file(dbg, fl.file, fl.line); 2575 | if (break_addr == (uint64_t)~0) { 2576 | printf("Unable to find line after %lu\n", fl.line); 2577 | return; 2578 | } 2579 | 2580 | Breakpoint *br = &dbg->break_table[dbg->break_len++]; 2581 | add_breakpoint(pid, br, break_addr, false); 2582 | printf("Set breakpoint @ 0x%lx\n", break_addr); 2583 | continue_to_next(dbg, pid, false); 2584 | } 2585 | 2586 | void goto_next_line(DebugState *dbg, int pid) { 2587 | struct user_regs_struct regs; 2588 | ptrace(PTRACE_GETREGS, pid, NULL, ®s); 2589 | 2590 | FileLine fl = {0}; 2591 | if (!get_approx_line_for_addr(dbg, regs.rip, &fl)) { 2592 | panic("failed to get line for rip!\n"); 2593 | } 2594 | 2595 | uint64_t break_addr = find_next_line_addr_in_file(dbg, fl.file, fl.line); 2596 | if (break_addr == (uint64_t)~0) { 2597 | printf("Unable to find line after %lu\n", fl.line); 2598 | return; 2599 | } 2600 | 2601 | Breakpoint *br = &dbg->break_table[dbg->break_len++]; 2602 | add_breakpoint(pid, br, break_addr, false); 2603 | printf("Set breakpoint @ 0x%lx\n", break_addr); 2604 | continue_to_next(dbg, pid, false); 2605 | } 2606 | 2607 | int start_debugger(char *bin_name, char **args) { 2608 | int pid = fork(); 2609 | if (pid == 0) { 2610 | ptrace(PTRACE_TRACEME, 0, NULL, NULL); 2611 | 2612 | // Turn off stack address randomization for more consistent debugging 2613 | personality(ADDR_NO_RANDOMIZE); 2614 | execvp(bin_name, args); 2615 | 2616 | panic("Failed to run program %s\n", bin_name); 2617 | } else if (pid < 0) { 2618 | panic("Failed to fork?\n"); 2619 | } 2620 | 2621 | int status; 2622 | waitpid(pid, &status, 0); 2623 | return pid; 2624 | } 2625 | 2626 | int process_command(DebugState *dbg, int *cpid, char *line, uint64_t line_size, dol_t *outbuf) { 2627 | struct user_regs_struct regs; 2628 | 2629 | int pid = *cpid; 2630 | 2631 | if (!strcmp(line, "c")) { 2632 | continue_to_next(dbg, pid, false); 2633 | } else if (line_size > 3 && line[0] == 'r' && line[1] == ' ') { 2634 | /* 2635 | free_debug_state(dbg); 2636 | 2637 | int ret = kill(pid, SIGKILL); 2638 | if (ret == -1) { 2639 | panic("Process too attached to children\n"); 2640 | } 2641 | 2642 | char path[PATH_MAX + 1] = {0}; 2643 | uint64_t path_size = line_size - 2; 2644 | memcpy(path, line + 2, path_size); 2645 | 2646 | char *args_arr[PATH_MAX + 1] = {0}; 2647 | int args_len = 0; 2648 | char *last_arg = path; 2649 | uint64_t i = 0; 2650 | for (;i < path_size; i++) { 2651 | if (path[i] == '\n' || path[i] == '\0') { 2652 | break; 2653 | } 2654 | 2655 | if (path[i] == ' ') { 2656 | args_arr[args_len++] = last_arg; 2657 | path[i] = '\0'; 2658 | last_arg = path + 1; 2659 | } 2660 | } 2661 | if (i == path_size) { 2662 | args_arr[args_len++] = last_arg; 2663 | path[i + 1] = '\0'; 2664 | } 2665 | 2666 | init_debug_state(dbg, args_arr[0]); 2667 | *cpid = start_debugger(args_arr[0], args_arr + 1); 2668 | */ 2669 | } else if (!strcmp(line, "r")) { 2670 | int ret = kill(pid, SIGKILL); 2671 | if (ret == -1) { 2672 | panic("Process too attached to children\n"); 2673 | } 2674 | 2675 | reset_checks(dbg); 2676 | 2677 | *cpid = start_debugger(dbg->sections.bin_name, dbg->sections.args); 2678 | } else if (line_size > 3 && line[0] == 'r' && line[1] == ' ') { 2679 | free_debug_state(dbg); 2680 | 2681 | int ret = kill(pid, SIGKILL); 2682 | if (ret == -1) { 2683 | panic("Process too attached to children\n"); 2684 | } 2685 | 2686 | char path[PATH_MAX + 1] = {0}; 2687 | uint64_t path_size = line_size - 2; 2688 | memcpy(path, line + 2, path_size); 2689 | 2690 | char *args_arr[PATH_MAX + 1] = {0}; 2691 | int args_len = 0; 2692 | char *last_arg = path; 2693 | uint64_t i = 0; 2694 | for (;i < path_size; i++) { 2695 | if (path[i] == '\n' || path[i] == '\0') { 2696 | break; 2697 | } 2698 | 2699 | if (path[i] == ' ') { 2700 | args_arr[args_len++] = last_arg; 2701 | path[i] = '\0'; 2702 | last_arg = path + 1; 2703 | } 2704 | } 2705 | if (i == path_size) { 2706 | args_arr[args_len++] = last_arg; 2707 | path[i + 1] = '\0'; 2708 | } 2709 | 2710 | *cpid = start_debugger(args_arr[0], args_arr + 1); 2711 | } else if (!strcmp(line, "fs")) { 2712 | uint64_t cu_count = 0; 2713 | for (uint64_t i = 0; i < dbg->block_len; i++) { 2714 | Block *b = &dbg->block_table[i]; 2715 | if (b->type == DW_TAG_compile_unit) { 2716 | printf("looking for files for %s\n", b->name); 2717 | 2718 | CULineTable *line_table = &dbg->line_tables[cu_count++]; 2719 | char *dir = NULL; 2720 | char *name = NULL; 2721 | 2722 | char tmp_path[PATH_MAX + 1] = {0}; 2723 | for (int j = 0; j < line_table->file_count; j++) { 2724 | uint8_t dir_idx = line_table->dir_idx[j]; 2725 | char *dirname = line_table->dirs[dir_idx]; 2726 | if (!line_table->filenames[j]) { 2727 | continue; 2728 | } 2729 | 2730 | printf("TESTING %s | %s == %s | %s\n", dirname, line_table->filenames[j], b->comp_dir, b->name); 2731 | sprintf(tmp_path, "%s/%s", dirname, line_table->filenames[j]); 2732 | if (!strcmp(line_table->filenames[j], b->name)) { 2733 | printf("FOUND %s | %s == %s\n", dirname, line_table->filenames[j], b->name); 2734 | dir = b->comp_dir; 2735 | name = b->name; 2736 | break; 2737 | } 2738 | } 2739 | 2740 | outbuf->offset += sprintf((char *)outbuf->data + outbuf->offset, "%s %s %s\n", "", dir, name); 2741 | if (outbuf->offset > outbuf->length) { 2742 | panic("too many file paths!\n"); 2743 | } 2744 | } 2745 | } 2746 | } else if (!strcmp(line, "pb")) { 2747 | for (uint64_t i = 0; i < dbg->break_len; i++) { 2748 | Breakpoint *bp = &dbg->break_table[i]; 2749 | 2750 | FileLine fl = {0}; 2751 | if (!get_approx_line_for_addr(dbg, bp->address, &fl)) { 2752 | outbuf->offset += sprintf((char *)outbuf->data + outbuf->offset, "0x%lx\n", bp->address); 2753 | } else { 2754 | outbuf->offset += sprintf((char *)outbuf->data + outbuf->offset, "0x%lx %lu %s\n", bp->address, fl.line, fl.file); 2755 | } 2756 | 2757 | if (outbuf->offset > outbuf->length) { 2758 | panic("too many breakpoints!\n"); 2759 | } 2760 | } 2761 | } else if (!strcmp(line, "pc")) { 2762 | ptrace(PTRACE_GETREGS, pid, NULL, ®s); 2763 | FileLine fl = {0}; 2764 | if (!get_approx_line_for_addr(dbg, regs.rip, &fl)) { 2765 | outbuf->offset += sprintf((char *)outbuf->data + outbuf->offset, "0x%llx\n", regs.rip); 2766 | } else { 2767 | outbuf->offset += sprintf((char *)outbuf->data + outbuf->offset, "0x%llx %lu %s\n", regs.rip, fl.line, fl.file); 2768 | } 2769 | } else if (!strcmp(line, "pw")) { 2770 | for (uint64_t i = 0; i < dbg->watch_len; i++) { 2771 | Watchpoint *wp = &dbg->watch_table[i]; 2772 | Block *var = &dbg->block_table[wp->var_idx]; 2773 | 2774 | if (wp->watch_width <= 8) { 2775 | uint64_t data = 0; 2776 | memcpy(&data, wp->new_data, wp->watch_width); 2777 | outbuf->offset += sprintf((char *)outbuf->data + outbuf->offset, "%lu %lu %s\n", i, data, var->name); 2778 | } else { 2779 | outbuf->offset += sprintf((char *)outbuf->data + outbuf->offset, "%lu 0x%lx %s\n", i, (uint64_t)wp->new_data, var->name); 2780 | } 2781 | 2782 | if (outbuf->offset > outbuf->length) { 2783 | panic("too many watchpoints!\n"); 2784 | } 2785 | } 2786 | } else if (line_size > 3 && line[0] == 'w' && line[1] == ' ') { 2787 | char *watch_name = line + 2; 2788 | add_watchpoint(dbg, pid, watch_name); 2789 | } else if (line_size > 3 && line[0] == 'b' && line[1] == ' ') { 2790 | char *break_str = line + 2; 2791 | 2792 | char *break_end; 2793 | uint64_t break_addr = strtol(break_str, &break_end, 0); 2794 | if (break_str == break_end) { 2795 | printf("Invalid breakpoint address: %s\n", break_str); 2796 | return 0; 2797 | } 2798 | 2799 | Breakpoint *br = &dbg->break_table[dbg->break_len++]; 2800 | add_breakpoint(pid, br, break_addr, true); 2801 | printf("Set breakpoint @ 0x%lx\n", break_addr); 2802 | } else if (line_size > 4 && line[0] == 'b' && line[1] == 'l' && line[2] == ' ') { 2803 | char *break_str = line + 3; 2804 | 2805 | char *break_end; 2806 | uint64_t break_line_num = strtol(break_str, &break_end, 0); 2807 | if (break_str == break_end) { 2808 | printf("Invalid line break: %s\n", break_str); 2809 | return 0; 2810 | } 2811 | 2812 | uint64_t rem_size = strlen(break_end); 2813 | printf("%s | %lu\n", break_end, rem_size); 2814 | if (break_end[0] != ' ') { 2815 | printf("command requires a file name\n"); 2816 | return 0; 2817 | } 2818 | 2819 | char *file_name = break_end + 1; 2820 | 2821 | uint64_t break_addr = find_line_addr_in_file(dbg, file_name, break_line_num); 2822 | if (break_addr == (uint64_t)~0) { 2823 | printf("Unable to find line %lu\n", break_line_num); 2824 | return 0; 2825 | } 2826 | 2827 | Breakpoint *br = &dbg->break_table[dbg->break_len++]; 2828 | add_breakpoint(pid, br, break_addr, true); 2829 | printf("Set breakpoint @ 0x%lx\n", break_addr); 2830 | } else if (!strcmp(line, "s")) { 2831 | goto_next_line(dbg, pid); 2832 | } else if (!strcmp(line, "si")) { 2833 | goto_next_flowline(dbg, pid); 2834 | } else if (!strcmp(line, "sa")) { 2835 | continue_to_next(dbg, pid, true); 2836 | } else if (!strcmp(line, "p")) { 2837 | gather_registers(pid, outbuf); 2838 | } else if (line_size > 3 && line[0] == 'p' && line[1] == ' ') { 2839 | char *var_name = line + 2; 2840 | 2841 | struct user_regs_struct regs; 2842 | ptrace(PTRACE_GETREGS, pid, NULL, ®s); 2843 | 2844 | CurrentScopes scopes = {0}; 2845 | if (!get_scopes(dbg, regs.rip, &scopes)) { 2846 | printf("Failed to find scope!\n"); 2847 | return 0; 2848 | } 2849 | 2850 | VarInfo info = get_var_for_name(dbg, &scopes, var_name); 2851 | if (info.var_idx == (uint64_t)~0) { 2852 | printf("Failed to find variable %s\n", var_name); 2853 | return 0; 2854 | } 2855 | 2856 | Block *var = &dbg->block_table[info.var_idx]; 2857 | 2858 | // Get variable address 2859 | DWExprMachine em = {0}; 2860 | em.frame_holder = &dbg->block_table[scopes.framed_scope_idx]; 2861 | DWExpression ex = { .expr = var->loc_expr, .len = var->loc_expr_len }; 2862 | eval_expr(®s, &em, ex, 0); 2863 | 2864 | uint64_t var_addr = em.val_stack[0]; 2865 | 2866 | if (info.member_idx) { 2867 | var_addr += info.member_offset; 2868 | } 2869 | 2870 | uint64_t cur_val = ptrace(PTRACE_PEEKDATA, pid, (void *)var_addr, NULL); 2871 | if (info.width <= 8) { 2872 | uint64_t data = 0; 2873 | memcpy(&data, &cur_val, info.width); 2874 | printf("%s == %lu, width: %lu\n", var_name, cur_val, info.width); 2875 | } else { 2876 | printf("%s chunk: %lx\n", var_name, cur_val); 2877 | } 2878 | } else if (!strcmp(line, "q")) { 2879 | happy_death("program closed!\n"); 2880 | } else { 2881 | printf("Command %s not recognized\n", line); 2882 | return 0; 2883 | } 2884 | 2885 | return 1; 2886 | } 2887 | 2888 | #ifdef LOCAL 2889 | int main(int argc, char **argv) { 2890 | if (argc < 2) { 2891 | panic("Please provide the debugger a program to debug!\n"); 2892 | } 2893 | 2894 | //print_block_table(dbg.block_table, dbg.block_len); 2895 | //print_line_table(dbg.line_tables, dbg.line_tables_len); 2896 | 2897 | DebugState dbg = {0}; 2898 | init_debug_state(&dbg, argv[1], argv + 1); 2899 | int pid = start_debugger(argv[1], argv + 1); 2900 | 2901 | char *cmds[] = { "fs" }; 2902 | uint64_t num_cmds = sizeof(cmds) / sizeof(*cmds); 2903 | 2904 | for (uint64_t i = 0; i < num_cmds; i++) { 2905 | uint8_t outbuf[OUTBUF_MAX] = {0}; 2906 | dol_t out = { .data = outbuf, .offset = 0, .length = OUTBUF_MAX }; 2907 | uint8_t packetbuf[OUTBUF_MAX + 5] = {0}; 2908 | dol_t packet = { .data = packetbuf, .offset = 0, .length = OUTBUF_MAX + 5 }; 2909 | 2910 | uint64_t cmd_size = strlen(cmds[i]); 2911 | printf("<%lu> RECV: [%.*s]\n", i, (int)cmd_size, cmds[i]); 2912 | 2913 | int ret = process_command(&dbg, &pid, cmds[i], cmd_size, &out); 2914 | 2915 | if (dbg.needs_restart) { 2916 | reset_checks(&dbg); 2917 | pid = start_debugger(dbg.sections.bin_name, dbg.sections.args); 2918 | dbg.needs_restart = false; 2919 | } 2920 | 2921 | if (ret) { 2922 | packet.offset = sprintf((char *)packet.data, "ok %lu %.*s", out.offset, (int)out.offset, out.data); 2923 | } else { 2924 | packet.offset = sprintf((char *)packet.data, "no %lu %.*s", out.offset, (int)out.offset, out.data); 2925 | } 2926 | 2927 | printf("<%lu> SEND: [%.*s]\n", i, (int)packet.offset, packet.data); 2928 | } 2929 | } 2930 | #else 2931 | int main(int argc, char **argv) { 2932 | if (argc < 2) { 2933 | panic("Please provide the debugger a program to debug!\n"); 2934 | } 2935 | 2936 | DebugState dbg = {0}; 2937 | init_debug_state(&dbg, argv[1], argv + 1); 2938 | int pid = start_debugger(argv[1], argv + 1); 2939 | 2940 | //print_block_table(dbg.block_table, dbg.block_len); 2941 | //print_line_table(dbg.line_tables, dbg.line_tables_len); 2942 | 2943 | struct sockaddr_in serv_addr = {0}; 2944 | uint16_t port = 5000; 2945 | int listen_fd = socket(AF_INET, SOCK_STREAM, 0); 2946 | serv_addr.sin_family = AF_INET; 2947 | serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 2948 | serv_addr.sin_port = htons(port); 2949 | 2950 | if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) < 0) { 2951 | panic("failed to set reuse addr?\n"); 2952 | } 2953 | 2954 | bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); 2955 | printf("Debugger listening on port %d\n", port); 2956 | listen(listen_fd, 1); 2957 | 2958 | int conn_fd = accept(listen_fd, (struct sockaddr *)NULL, NULL); 2959 | 2960 | 2961 | uint8_t outbuf[OUTBUF_MAX] = {0}; 2962 | dol_t out = { .data = outbuf, .offset = 0, .length = OUTBUF_MAX }; 2963 | uint8_t packetbuf[OUTBUF_MAX + 5] = {0}; 2964 | dol_t packet = { .data = packetbuf, .offset = 0, .length = OUTBUF_MAX + 5 }; 2965 | 2966 | char line_buffer[LINE_BUFFER_MAX + 1]; 2967 | uint64_t used_bytes = 0; 2968 | for (;;) { 2969 | uint64_t rem_bytes = LINE_BUFFER_MAX - used_bytes; 2970 | if (!rem_bytes) { 2971 | panic("input buffer full!\n"); 2972 | } 2973 | 2974 | ssize_t ret_size = recv(conn_fd, line_buffer + used_bytes, rem_bytes, 0); 2975 | if (!ret_size) { 2976 | happy_death("GUI server closed!\n"); 2977 | } else if (ret_size < 0) { 2978 | panic("recv failed!\n"); 2979 | } 2980 | 2981 | uint64_t line_start = used_bytes; 2982 | uint64_t new_start = line_start; 2983 | used_bytes += ret_size; 2984 | 2985 | for (uint64_t i = line_start; i < used_bytes; i++) { 2986 | if (line_buffer[i] == '\n') { 2987 | line_buffer[i] = 0; 2988 | // printf("RECV %lu [%.*s]\n", i - new_start, (int)(i - new_start), line_buffer + new_start); 2989 | 2990 | int ret = process_command(&dbg, &pid, line_buffer + new_start, i - new_start, &out); 2991 | 2992 | if (dbg.needs_restart) { 2993 | reset_checks(&dbg); 2994 | pid = start_debugger(dbg.sections.bin_name, dbg.sections.args); 2995 | dbg.needs_restart = false; 2996 | } 2997 | 2998 | new_start = i + 1; 2999 | 3000 | if (out.offset > out.length) { 3001 | panic("outbound buffer too full!\n"); 3002 | } 3003 | 3004 | if (ret) { 3005 | packet.offset = sprintf((char *)packet.data, "ok %lu %.*s", out.offset, (int)out.offset, out.data); 3006 | } else { 3007 | packet.offset = sprintf((char *)packet.data, "no %lu %.*s", out.offset, (int)out.offset, out.data); 3008 | } 3009 | 3010 | // printf("SEND %lu [%s]\n", packet.offset, packet.data); 3011 | 3012 | ssize_t ret_size = send(conn_fd, packet.data, packet.offset, 0); 3013 | if (!ret_size) { 3014 | happy_death("GUI server closed!\n"); 3015 | } else if (ret_size < 0) { 3016 | panic("recv failed!\n"); 3017 | } 3018 | 3019 | memset(out.data, 0, ret_size); 3020 | memset(packet.data, 0, packet.offset); 3021 | out.offset = 0; 3022 | packet.offset = 0; 3023 | } 3024 | } 3025 | 3026 | memmove(line_buffer, line_buffer + line_start, new_start - line_start); 3027 | used_bytes -= (new_start - line_start); 3028 | 3029 | } 3030 | } 3031 | #endif 3032 | -------------------------------------------------------------------------------- /media/debug.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colrdavidson/debug/0957db76f4feef04d32ba401bc4ad216e69f31f4/media/debug.gif -------------------------------------------------------------------------------- /tests/asm_test/.gitignore: -------------------------------------------------------------------------------- 1 | asm_test 2 | *.o 3 | -------------------------------------------------------------------------------- /tests/asm_test/build.sh: -------------------------------------------------------------------------------- 1 | nasm -f elf64 -gdwarf main.s 2 | ld -o asm_test main.o 3 | -------------------------------------------------------------------------------- /tests/asm_test/main.s: -------------------------------------------------------------------------------- 1 | [bits 64] 2 | 3 | global _start 4 | 5 | section .text 6 | _start: 7 | mov rdi, 21 8 | mov rax, 60 9 | syscall 10 | nop 11 | -------------------------------------------------------------------------------- /tests/build.sh: -------------------------------------------------------------------------------- 1 | for dir in $(find . -mindepth 1 -maxdepth 1 -type d) 2 | do 3 | cd "$dir" 4 | echo "Building $dir" 5 | ./build.sh 6 | cd - > /dev/null 7 | done 8 | -------------------------------------------------------------------------------- /tests/complex_structs/.gitignore: -------------------------------------------------------------------------------- 1 | structs 2 | -------------------------------------------------------------------------------- /tests/complex_structs/build.sh: -------------------------------------------------------------------------------- 1 | clang -O0 -g -o structs structs.c 2 | -------------------------------------------------------------------------------- /tests/complex_structs/structs.c: -------------------------------------------------------------------------------- 1 | typedef struct { 2 | int apple; 3 | int carrot; 4 | int beet; 5 | int banana; 6 | } Foobar; 7 | 8 | struct BigStruct { 9 | long long f1; 10 | long long f2; 11 | long long f3; 12 | long long f4; 13 | long long f5; 14 | long long f6; 15 | long long f7; 16 | long long f8; 17 | }; 18 | 19 | typedef struct { 20 | Foobar bar; 21 | int baloney; 22 | } Structification; 23 | 24 | int main() { 25 | // struct BigStruct bs = {0}; 26 | 27 | Structification stuckt = {0}; 28 | stuckt.bar.banana = 2; 29 | stuckt.bar.beet = 8; 30 | stuckt.bar.banana += stuckt.bar.beet; 31 | /* 32 | foo.banana = 5; 33 | foo.beet = 9; 34 | foo.carrot = 5; 35 | foo.beet += bs.f8; 36 | 37 | foo.apple += foo.banana; 38 | foo.banana += foo.apple; 39 | bs.f3 -= foo.carrot; 40 | */ 41 | 42 | return stuckt.bar.beet + stuckt.bar.apple; 43 | // return foo.apple + foo.banana + foo.beet + foo.carrot + bs.f3 + bs.f8; 44 | } 45 | -------------------------------------------------------------------------------- /tests/file_collide/.gitignore: -------------------------------------------------------------------------------- 1 | render 2 | -------------------------------------------------------------------------------- /tests/file_collide/build.sh: -------------------------------------------------------------------------------- 1 | clang -O0 -g -o render file.c collide/file.c 2 | -------------------------------------------------------------------------------- /tests/file_collide/collide/file.c: -------------------------------------------------------------------------------- 1 | int render_butter(int test) { 2 | return test + 1; 3 | } 4 | -------------------------------------------------------------------------------- /tests/file_collide/common.h: -------------------------------------------------------------------------------- 1 | int render_butter(int test); 2 | -------------------------------------------------------------------------------- /tests/file_collide/file.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | int main() { 4 | int foo = render_butter(5); 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /tests/func_shadows/.gitignore: -------------------------------------------------------------------------------- 1 | shadows 2 | -------------------------------------------------------------------------------- /tests/func_shadows/build.sh: -------------------------------------------------------------------------------- 1 | clang -O0 -g -static -o shadows shadows.c def1.c def2.c 2 | -------------------------------------------------------------------------------- /tests/func_shadows/def1.c: -------------------------------------------------------------------------------- 1 | int shadow_test(int foo) { 2 | foo += 1; 3 | return foo; 4 | } 5 | -------------------------------------------------------------------------------- /tests/func_shadows/def2.c: -------------------------------------------------------------------------------- 1 | static int shadow_test(short bar, short baz) { 2 | bar += 3; 3 | baz += 5; 4 | return bar + baz; 5 | } 6 | 7 | int extra_add(int foo) { 8 | return shadow_test(foo, foo) + 1; 9 | } 10 | -------------------------------------------------------------------------------- /tests/func_shadows/shadows.c: -------------------------------------------------------------------------------- 1 | int shadow_test(int); 2 | 3 | int main() { 4 | int foo = 2; 5 | return shadow_test(foo); 6 | } 7 | -------------------------------------------------------------------------------- /tests/multiple_cu/.gitignore: -------------------------------------------------------------------------------- 1 | dummy 2 | -------------------------------------------------------------------------------- /tests/multiple_cu/build.sh: -------------------------------------------------------------------------------- 1 | clang -O0 -g -o dummy dummy.c extra.c 2 | -------------------------------------------------------------------------------- /tests/multiple_cu/dummy.c: -------------------------------------------------------------------------------- 1 | struct Foobar { 2 | int ret; 3 | }; 4 | 5 | void super(int *); 6 | 7 | int main(void) { 8 | struct Foobar foo = { .ret = 2 }; 9 | foo.ret += 1; 10 | 11 | super(&foo.ret); 12 | 13 | int ret2; 14 | { 15 | int ret = 5; 16 | super(&ret); 17 | 18 | ret2 = ret; 19 | } 20 | 21 | return ret2 + foo.ret; 22 | } 23 | -------------------------------------------------------------------------------- /tests/multiple_cu/extra.c: -------------------------------------------------------------------------------- 1 | void super(int *data) { 2 | int fizz = 2; 3 | *data += 1 + fizz; 4 | } 5 | -------------------------------------------------------------------------------- /tests/simple/.gitignore: -------------------------------------------------------------------------------- 1 | simple 2 | -------------------------------------------------------------------------------- /tests/simple/build.sh: -------------------------------------------------------------------------------- 1 | clang -O0 -g -o simple simple.c 2 | -------------------------------------------------------------------------------- /tests/simple/simple.c: -------------------------------------------------------------------------------- 1 | int main() { 2 | return 0; 3 | } 4 | -------------------------------------------------------------------------------- /tests/simple_funcs/.gitignore: -------------------------------------------------------------------------------- 1 | simple_funcs 2 | -------------------------------------------------------------------------------- /tests/simple_funcs/build.sh: -------------------------------------------------------------------------------- 1 | clang -O0 -g -o simple_funcs simple.c 2 | -------------------------------------------------------------------------------- /tests/simple_funcs/simple.c: -------------------------------------------------------------------------------- 1 | void test_func(int *a) { 2 | *a += 3; 3 | } 4 | 5 | int main() { 6 | int foo = 0; 7 | test_func(&foo); 8 | test_func(&foo); 9 | return foo; 10 | } 11 | -------------------------------------------------------------------------------- /tests/var_shadows/.gitignore: -------------------------------------------------------------------------------- 1 | shadows 2 | -------------------------------------------------------------------------------- /tests/var_shadows/build.sh: -------------------------------------------------------------------------------- 1 | clang -O0 -g -o shadows shadows.c 2 | -------------------------------------------------------------------------------- /tests/var_shadows/shadows.c: -------------------------------------------------------------------------------- 1 | int main() { 2 | int foo = 502; 3 | int bar = 3; 4 | 5 | { 6 | int bar = 5; 7 | foo += bar; 8 | bar += 1; 9 | foo += bar; 10 | } 11 | 12 | return foo; 13 | } 14 | --------------------------------------------------------------------------------