Análise dinâmica com tracing no QEMU

TL;DR


1. Compile um alvo para trace

Crie:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// src/trace_target.c
#include "types.h"
#include "uart.h"

volatile u32 g_state = 0u;

static void step(u32 x) {
  g_state ^= (x + 0x1234u);
}

int main(void) {
  for (u32 i = 0u; i < 5u; i++) {
    step(i);
  }
  uart_puts("state=");
  uart_puthex32(g_state);
  uart_putc('\n');
  return 0;
}

Compile:

1
2
3
riscv64-unknown-elf-gcc -O0 -g -ffreestanding -nostdlib \
  -march=rv32im -mabi=ilp32 -T src/link.ld \
  src/start.s src/uart.c src/trace_target.c -o build/trace_target.elf

2. Trace de instruções com QEMU

1
2
3
qemu-system-riscv32 -M virt -nographic -bios none \
  -kernel build/trace_target.elf \
  -d in_asm,exec -D build/trace.log

Agora inspecione build/trace.log para ver o fluxo de instruções.


3. Correlacione linhas do trace com o disassembly

1
riscv64-unknown-elf-objdump -d -M numeric,no-aliases build/trace_target.elf | less

Use os valores de PC no log para saltar para as linhas de disassembly correspondentes.


Exercícios

  1. Adicione outra função e verifique se ela aparece no trace.
  2. Compile com -O2 e compare o comprimento do trace.
  3. Adicione um branch condicional e veja como isso muda o trace.

Resumo