Files
nunuhara_xsystem4/src/debug_info.c
T
kichikuou cb551e8f06 debugger_dap: Source-level debugging
- Add support for setBreakpoints request
- Add source locations to stackTrace response
- Source-level stepIn/stepOut/next executions

All of these only work if debug information is available. Otherwise they
work at the instruction level as before.
2025-05-10 15:31:04 +09:00

233 lines
6.4 KiB
C

/* Copyright (C) 2024 kichikuou <KichikuouChrome@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "system4/file.h"
#include "cJSON.h"
#include "debugger.h"
struct mapping {
int addr;
int file;
int line;
};
struct source_content {
int nr_lines;
char *lines[];
};
struct dbg_info {
char *src_root;
int nr_sources;
char **sources;
struct source_content **source_contents;
int nr_mappings;
struct mapping *mappings;
};
struct dbg_info *dbg_info_load(const char *path)
{
char *json_text = file_read(path, NULL);
if (!json_text) {
DBG_ERROR("Cannot load debug information file %s: %s", path, strerror(errno));
return NULL;
}
cJSON *root = cJSON_Parse(json_text);
free(json_text);
cJSON *version = cJSON_GetObjectItem(root, "version");
if (!cJSON_IsString(version) || strcmp(version->valuestring, "alpha-1")) {
DBG_ERROR("%s: Unsupported debug information format", display_utf0(path));
cJSON_Delete(root);
return NULL;
}
struct dbg_info *info = xcalloc(1, sizeof(struct dbg_info));
cJSON *sources = cJSON_GetObjectItem(root, "sources");
info->nr_sources = cJSON_GetArraySize(sources);
info->sources = xcalloc(info->nr_sources, sizeof(char *));
cJSON *source;
int i = 0;
cJSON_ArrayForEach(source, sources) {
info->sources[i++] = xstrdup(source->valuestring);
}
cJSON *mappings = cJSON_GetObjectItem(root, "mappings");
info->nr_mappings = cJSON_GetArraySize(mappings);
info->mappings = xcalloc(info->nr_mappings, sizeof(struct mapping));
struct mapping *m = info->mappings;
cJSON *item;
cJSON_ArrayForEach(item, mappings) {
m->addr = cJSON_GetArrayItem(item, 0)->valueint;
m->file = cJSON_GetArrayItem(item, 1)->valueint;
m->line = cJSON_GetArrayItem(item, 2)->valueint;
m++;
}
cJSON_Delete(root);
info->src_root = xstrdup(path_dirname(path));
info->source_contents = xcalloc(info->nr_sources, sizeof(struct source_content *));
return info;
}
static struct source_content *load_source_content(const char *path)
{
char *text = file_read(path, NULL);
if (!text) {
DBG_ERROR("Cannot load source file %s", path);
return NULL;
}
int nr_lines = 1; // count the last line even if it doesn't end with '\n'
for (char *p = text; *p; p++) {
if (*p == '\n')
nr_lines++;
}
struct source_content *content = xcalloc(1, sizeof(struct source_content) + nr_lines * sizeof(char *));
content->nr_lines = nr_lines;
for (int i = 0;; i++) {
content->lines[i] = text;
char *next = strchr(text, '\n');
if (!next)
break;
*next = '\0';
text = next + 1;
}
return content;
}
const char *dbg_info_source_name(const struct dbg_info *info, int file)
{
if (file < 0 || file >= info->nr_sources)
return NULL;
return info->sources[file];
}
char *dbg_info_source_path(const struct dbg_info *info, int file)
{
if (file < 0 || file >= info->nr_sources)
return NULL;
return path_join(info->src_root, info->sources[file]);
}
const char *dbg_info_source_line(const struct dbg_info *info, int file, int line)
{
if (file < 0 || file >= info->nr_sources)
return NULL;
if (!info->source_contents[file]) {
char *path = dbg_info_source_path(info, file);
info->source_contents[file] = load_source_content(path);
free(path);
if (!info->source_contents[file])
return NULL;
}
if (line <= 0 || line > info->source_contents[file]->nr_lines)
return NULL;
return info->source_contents[file]->lines[line - 1];
}
int dbg_info_find_file(const struct dbg_info *info, const char *filename)
{
if (is_absolute_path(filename)) {
char *target = realpath_utf8(filename); // Convert '\\' to '/' on Windows
char *prefix = realpath_utf8(info->src_root);
for (int i = 0; i < info->nr_sources; i++) {
char *path = path_join(prefix, info->sources[i]);
if (!strcasecmp(path, target)) {
free(path);
free(prefix);
free(target);
return i;
}
free(path);
}
free(prefix);
free(target);
} else {
for (int i = 0; i < info->nr_sources; i++) {
if (!strcasecmp(info->sources[i], filename) ||
!strcasecmp(path_basename(info->sources[i]), filename)) {
return i;
}
}
}
return -1;
}
bool dbg_info_addr2line(const struct dbg_info *info, int addr, int *file, int *line)
{
// Find the last mapping whose address is less than or equal to `addr`.
int left = 0, right = info->nr_mappings;
while (left < right) {
int mid = (left + right) / 2;
if (info->mappings[mid].addr <= addr)
left = mid + 1;
else
right = mid;
}
if (left == 0) {
assert(info->nr_mappings == 0 || addr < info->mappings[0].addr);
return false;
}
assert(info->mappings[left - 1].addr <= addr);
assert(left == info->nr_mappings || addr < info->mappings[left].addr);
if (file)
*file = info->mappings[left - 1].file;
if (line)
*line = info->mappings[left - 1].line;
return true;
}
int dbg_info_line2addr(const struct dbg_info *info, int file, int line)
{
if (file < 0 || file >= info->nr_sources || line <= 0)
return -1;
// Find the first mapping whose file is `file` and line is greater than or equal to `line`.
int left = 0, right = info->nr_mappings;
while (left < right) {
int mid = (left + right) / 2;
if (info->mappings[mid].file < file || (info->mappings[mid].file == file && info->mappings[mid].line < line))
left = mid + 1;
else
right = mid;
}
if (left == info->nr_mappings) {
assert(info->nr_mappings == 0 || info->mappings[info->nr_mappings - 1].file < file ||
(info->mappings[info->nr_mappings - 1].file == file && info->mappings[info->nr_mappings - 1].line < line));
return -1;
}
assert(left == 0 || info->mappings[left - 1].file < file ||
(info->mappings[left - 1].file == file && info->mappings[left - 1].line < line));
if (file < info->mappings[left].file)
return -1;
assert(file == info->mappings[left].file && line <= info->mappings[left].line);
return info->mappings[left].addr;
}