Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,28 @@
clean:
$(MAKE) -f Makefile.mingw32 clean
else
all: bin/fsclient bin/ps2client
all: bin/fsclient bin/ps2client bin/ps2symbolize

clean:
rm -f obj/*.o bin/*client*
rm -f obj/*.o bin/*client* bin/ps2symbolize bin/test_symbolize
endif

ifneq ($(OS),Windows_NT)
install: bin/ps2symbolize
endif

install: bin/fsclient bin/ps2client
strip bin/*client*
@mkdir -p $(DESTDIR)$(PREFIX)/bin
cp bin/*client* $(DESTDIR)$(PREFIX)/bin
ifneq ($(OS),Windows_NT)
strip bin/ps2symbolize
cp bin/ps2symbolize $(DESTDIR)$(PREFIX)/bin
endif

check: bin/ps2client bin/smoke bin/fake_ps2link bin/crc32
check: bin/ps2client bin/smoke bin/fake_ps2link bin/crc32 bin/ps2symbolize bin/test_symbolize
bin/smoke $(CURDIR)/bin/ps2client
bin/test_symbolize

####################
## CLIENT MODULES ##
Expand Down Expand Up @@ -90,3 +99,11 @@
bin/crc32: tests/crc32.c
@mkdir -p bin
$(CC) $(CFLAGS) $< -o $@

bin/ps2symbolize: tools/ps2symbolize.c
@mkdir -p bin
$(CC) $(CFLAGS) $< -o $@

bin/test_symbolize: tests/test_symbolize.c tools/ps2symbolize.c
@mkdir -p bin
$(CC) $(CFLAGS) $< -o $@
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,25 @@ old one: do not ping while another client is serving a running program.
Commands that load host files (`execee`, `execiop`, ...) do the same check
before they send the command, instead of waiting a fixed time.

### SYMBOLIZING EXCEPTION LOGS

The optional `bin/ps2symbolize` annotates ps2link's EE/IOP exception logs
with function names and source locations from matching **unstripped** ELF files.
It does not change ps2link or require a new wire protocol. For example:

```sh
ps2client console | bin/ps2symbolize --ee game.elf
bin/ps2symbolize --ee game.elf crash.log
bin/ps2symbolize --iop mydriver=driver.elf crash.log
```

The C tool uses the PS2DEV `mips64r5900el-ps2-elf-addr2line` and
`mipsel-none-elf-addr2line` tools found on `PATH`; override them with
`--ee-tool` or `--iop-tool`. For IOP modules, the file must have debug symbols
and the logged **relative EPC** must match its ELF section addresses. Keep the
ELFs from the exact build being debugged. Unresolved locations are left as
original log text.

### TESTS

`make check` builds and runs plain-C portable smoke tests (including invalid
Expand Down
51 changes: 51 additions & 0 deletions tests/test_symbolize.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/* Unit tests for EE/IOP exception log parsing. */
#define PS2_SYMBOLIZE_NO_MAIN
#include "../tools/ps2symbolize.c"

static uint32_t last_address;
static const char *last_elf;
static int calls;
static int fake_resolver(const char *tool, const char *elf, uint32_t address,
char *output, size_t capacity, void *context)
{
(void)tool; (void)context;
last_elf = elf; last_address = address; calls++;
snprintf(output, capacity, "updateSprites at renderer.c:284");
return 1;
}

static void test_symbolizer(void)
{
struct symbolizer config = {0};
FILE *output = tmpfile();
char text[1024];
size_t length;
if (!output) { perror("tmpfile"); exit(1); }
config.ee_tool = "ee-addr2line";
config.iop_tool = "iop-addr2line";
config.ee_elf = "game.elf";
config.module_count = 1;
strcpy(config.modules[0].name, "mydriver");
config.modules[0].elf = "driver.elf";
annotate_line(&config, "EE Exception handler: Address load exception\n",
output, fake_resolver, NULL);
annotate_line(&config, "Cause 00000010 EPC 00123456\n",
output, fake_resolver, NULL);
if (calls != 1 || last_address != 0x123456 ||
strcmp(last_elf, "game.elf")) exit(1);
annotate_line(&config, "IOP Exception handler: Address store\n",
output, fake_resolver, NULL);
annotate_line(&config, "Module Name \"mydriver\" Relative EPC 00000064\n",
output, fake_resolver, NULL);
if (calls != 2 || last_address != 100 ||
strcmp(last_elf, "driver.elf")) exit(1);
rewind(output);
length = fread(text, 1, sizeof(text) - 1, output);
text[length] = 0;
if (!strstr(text, "[EE symbol]") || !strstr(text, "[IOP mydriver symbol]"))
exit(1);
fclose(output);
puts("PASS EE/IOP symbolization, original log preservation");
}

int main(void) { test_symbolizer(); return 0; }
171 changes: 171 additions & 0 deletions tools/ps2symbolize.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/* Annotate ps2link EE/IOP exception logs with matching ELF debug symbols. */
#define _POSIX_C_SOURCE 200809L
#include <ctype.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <spawn.h>

extern char **environ;

struct module_map { char name[128]; const char *elf; };
struct symbolizer {
const char *ee_elf;
const char *ee_tool;
const char *iop_tool;
struct module_map modules[64];
size_t module_count;
int exception; /* 0 none, 1 EE, 2 IOP */
int errors;
};

typedef int (*resolve_fn)(const char *, const char *, uint32_t,
char *, size_t, void *);

static int equals_ignore_case(const char *a, const char *b)
{
while (*a && *b) {
if (tolower((unsigned char)*a) != tolower((unsigned char)*b)) return 0;
a++; b++;
}
return !*a && !*b;
}

/* addr2line is invoked directly, without a shell or string interpolation. */
#ifndef PS2_SYMBOLIZE_NO_MAIN
static int resolve_symbol(const char *tool, const char *elf, uint32_t address,
char *output, size_t capacity, void *unused)
{
int pipefd[2], status;
pid_t pid;
FILE *stream;
char pc[16];
char *const argv[] = {(char *)tool, "-f", "-C", "-p", "-e",
(char *)elf, pc, NULL};
posix_spawn_file_actions_t actions;
(void)unused;
snprintf(pc, sizeof(pc), "0x%08x", address);
if (pipe(pipefd) < 0) return -1;
if (posix_spawn_file_actions_init(&actions) != 0) {
close(pipefd[0]); close(pipefd[1]); return -1;
}
posix_spawn_file_actions_adddup2(&actions, pipefd[1], STDOUT_FILENO);
posix_spawn_file_actions_addclose(&actions, pipefd[0]);
posix_spawn_file_actions_addclose(&actions, pipefd[1]);
status = posix_spawnp(&pid, tool, &actions, NULL, argv, environ);
posix_spawn_file_actions_destroy(&actions);
close(pipefd[1]);
if (status != 0) { close(pipefd[0]); return -1; }
stream = fdopen(pipefd[0], "r");
if (!stream) { close(pipefd[0]); waitpid(pid, &status, 0); return -1; }
if (!fgets(output, (int)capacity, stream)) output[0] = '\0';
fclose(stream);
if (waitpid(pid, &status, 0) < 0 || !WIFEXITED(status) || WEXITSTATUS(status))
return -1;
output[strcspn(output, "\r\n")] = '\0';
if (!output[0] || strncmp(output, "??", 2) == 0) return 0;
return 1;
}
#endif

static void annotate_line(struct symbolizer *config, const char *line,
FILE *out, resolve_fn resolve, void *context)
{
const char *elf = NULL, *tool = NULL, *epc;
uint32_t pc;
char name[128] = "", symbol[1024];
int value;

fputs(line, out);
if (strstr(line, "EE Exception handler:") != NULL) config->exception = 1;
if (strstr(line, "IOP Exception handler:") != NULL) config->exception = 2;
if (config->exception == 1 && config->ee_elf) {
epc = strstr(line, "EPC ");
if (!epc || sscanf(epc, "EPC %x", &pc) != 1) return;
elf = config->ee_elf;
tool = config->ee_tool;
config->exception = 0;
} else if (config->exception == 2) {
size_t i;
epc = strstr(line, "Module Name \"");
if (!epc || sscanf(epc, "Module Name \"%127[^\"]\" Relative EPC %x",
name, &pc) != 2) return;
for (i = 0; i < config->module_count; i++) {
if (equals_ignore_case(name, config->modules[i].name)) {
elf = config->modules[i].elf;
break;
}
}
tool = config->iop_tool;
config->exception = 0;
} else {
return;
}
if (!elf) return;
value = resolve(tool, elf, pc, symbol, sizeof(symbol), context);
if (value > 0) {
if (name[0]) fprintf(out, " [IOP %s symbol] %s\n", name, symbol);
else fprintf(out, " [EE symbol] %s\n", symbol);
} else if (value < 0) {
fprintf(stderr, "ps2symbolize: cannot execute %s for %s\n", tool, elf);
config->errors++;
}
}

#ifndef PS2_SYMBOLIZE_NO_MAIN
static void usage(FILE *out)
{
fputs("Usage: ps2symbolize [--ee ELF] [--iop MODULE=ELF]... "
"[--ee-tool PATH] [--iop-tool PATH] [LOG|-]\n", out);
}

int main(int argc, char **argv)
{
struct symbolizer config = {0};
FILE *input = stdin;
char line[8192];
const char *log_path = NULL;
int i;
config.ee_tool = "mips64r5900el-ps2-elf-addr2line";
config.iop_tool = "mipsel-none-elf-addr2line";
setvbuf(stdout, NULL, _IOLBF, 0);
for (i = 1; i < argc; i++) {
if ((!strcmp(argv[i], "--ee") || !strcmp(argv[i], "--iop") ||
!strcmp(argv[i], "--ee-tool") || !strcmp(argv[i], "--iop-tool")) &&
i + 1 >= argc) { usage(stderr); return 2; }
if (!strcmp(argv[i], "--ee")) config.ee_elf = argv[++i];
else if (!strcmp(argv[i], "--ee-tool")) config.ee_tool = argv[++i];
else if (!strcmp(argv[i], "--iop-tool")) config.iop_tool = argv[++i];
else if (!strcmp(argv[i], "--iop")) {
const char *pair = argv[++i];
const char *sep = strchr(pair, '=');
size_t length = sep ? (size_t)(sep - pair) : 0;
struct module_map *entry;
if (!length || length >= sizeof(config.modules[0].name) ||
!sep[1] || config.module_count == 64) { usage(stderr); return 2; }
entry = &config.modules[config.module_count++];
memcpy(entry->name, pair, length);
entry->name[length] = '\0';
entry->elf = sep + 1;
} else if (!strcmp(argv[i], "--help")) { usage(stdout); return 0; }
else if (!log_path && (argv[i][0] != '-' || !strcmp(argv[i], "-"))) {
log_path = argv[i];
} else { usage(stderr); return 2; }
}
if (!config.ee_elf && !config.module_count) { usage(stderr); return 2; }
if (log_path && strcmp(log_path, "-")) {
input = fopen(log_path, "r");
if (!input) { perror(log_path); return 1; }
}
while (fgets(line, sizeof(line), input)) {
annotate_line(&config, line, stdout, resolve_symbol, NULL);
}
if (input != stdin) fclose(input);
return config.errors ? 1 : 0;
}
#endif
Loading