├── .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 | 
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 |
--------------------------------------------------------------------------------