Salida limpia del emulador
Al ejecutar make run y luego introducir q para salir, se producía un error debido a que la función is_exit_status_bad devolvía -1. Para evitarlo, basta con establecer el estado del emulador a NEMU_QUIT cuando se recibe el comando q:
// nemu/src/monitor/sdb/sdb.c
void sdb_mainloop() {
...
int i;
for (i = 0; i < NR_CMD; i ++) {
if (strcmp(cmd, cmd_table[i].name) == 0) {
if (cmd_table[i].handler(args) < 0) {
if (strcmp(cmd, "q") == 0) {
nemu_state.state = NEMU_QUIT;
}
return;
}
break;
}
}
if (i == NR_CMD) { printf("Comando desconocido '%s'\n", cmd); }
}
}
Depurador simple
Ejecución paso a paso: si [N]
Registramos el comando en la tabla:
static struct {
const char *name;
const char *description;
int (*handler) (char *);
} cmd_table [] = {
{ "help", "Muestra información de comandos", cmd_help },
{ "c", "Continúa la ejecución", cmd_c },
{ "q", "Sale de NEMU", cmd_q },
{ "si", "Ejecuta N instrucciones (por defecto 1)", cmd_si },
// Más comandos...
};
Implementamos cmd_si:
static int cmd_si(char *args) {
char *arg = strtok(NULL, " ");
int pasos = (arg == NULL) ? 1 : strtol(arg, NULL, 10);
cpu_exec(pasos);
return 0;
}
Mostrar registros: info r
Añadimos el comando info y su subcomando r:
{ "info", "Muestra info de registros y puntos de vigilancia", cmd_info },
static int cmd_info(char *args) {
char *arg = strtok(NULL, " ");
if (arg == NULL) {
printf("Uso: info r (registros) o info w (puntos de vigilancia)\n");
} else if (strcmp(arg, "r") == 0) {
isa_reg_display();
} else if (strcmp(arg, "w") == 0) {
// pendiente
} else {
printf("Uso: info r (registros) o info w (puntos de vigilancia)\n");
}
return 0;
}
Implementamos isa_reg_display (en nemu/src/isa/$ISA/reg.c):
void isa_reg_display() {
int num_regs = ARRLEN(regs);
for (int i = 0; i < num_regs; i++) {
printf("%-8s%-#20lx%-20ld\n", regs[i], cpu.gpr[i], cpu.gpr[i]);
}
}
Explorar memoria: x N EXPR
Registramos el comando:
{ "x", "Uso: x N EXPR. Examina N bytes desde la dirección EXPR", cmd_x },
static int cmd_x(char *args) {
char *arg1 = strtok(NULL, " ");
if (!arg1) { printf("Uso: x N EXPR\n"); return 0; }
char *arg2 = strtok(NULL, " ");
if (!arg2) { printf("Uso: x N EXPR\n"); return 0; }
int n = strtol(arg1, NULL, 10);
vaddr_t dir = strtol(arg2, NULL, 16);
for (int i = 0; i < n; ) {
printf(ANSI_FMT("%#018lx: ", ANSI_FG_CYAN), dir);
for (int j = 0; i < n && j < 4; i++, j++) {
word_t val = vaddr_read(dir, 8);
dir += 8;
printf("%#018lx ", val);
}
putchar('\n');
}
return 0;
}
Evaluar expresiones: p EXPR
Comando
{ "p", "Uso: p EXPR. Calcula la expresión, ej. p $eax + 1", cmd_p }
static int cmd_p(char* args) {
bool exito;
word_t res = expr(args, &exito);
if (!exito) {
puts("expresión inválida");
} else {
printf("%lu\n", res);
}
return 0;
}
Analizador léxico y evaluador
word_t expr(char *e, bool *exito) {
if (!make_token(e)) {
*exito = false;
return 0;
}
return eval(0, nr_token-1, exito);
}
make_token recorre la cadena aplicando epxresiones regulares para extraer tokens:
static bool make_token(char *e) {
int pos = 0;
regmatch_t pmatch;
nr_token = 0;
while (e[pos] != '\0') {
int i;
for (i = 0; i < NR_REGEX; i++) {
int res = regexec(&re[i], e + pos, 1, &pmatch, 0);
if (res == 0 && pmatch.rm_so == 0) {
char *inicio = e + pos;
int largo = pmatch.rm_eo;
pos += largo;
if (rules[i].token_type != TK_NOTYPE) {
tokens[nr_token].type = rules[i].token_type;
switch (rules[i].token_type) {
case TK_NUM:
case TK_REG:
case TK_VAR:
strncpy(tokens[nr_token].str, inicio, largo);
tokens[nr_token].str[largo] = '\0';
break;
}
nr_token++;
}
break;
}
}
if (i == NR_REGEX) {
printf("sin coincidencia en posición %d\n%s\n%*.s^\n", pos, e, pos, "");
return false;
}
}
return true;
}
Definiciones de tipos de token:
enum {
TK_NOTYPE = 256, TK_EQ,
TK_NUM,
TK_REG,
TK_VAR,
};
static struct rule {
const char *regex;
int token_type;
} rules[] = {
{" +", TK_NOTYPE},
{"\\+", '+'}, {"-", '-'}, {"\\*", '*'}, {"/", '/'},
{"==", TK_EQ},
{"\\(", '('}, {"\\)", ')'},
{"[0-9]+", TK_NUM},
{"\\$\\w+", TK_REG},
{"[A-Za-z_]\\w*", TK_VAR},
};
Función eval:
word_t eval(int p, int q, bool *ok) {
*ok = true;
if (p > q) { *ok = false; return 0; }
if (p == q) {
if (tokens[p].type != TK_NUM) { *ok = false; return 0; }
return strtol(tokens[p].str, NULL, 10);
}
if (check_parentheses(p, q)) return eval(p+1, q-1, ok);
int major = find_major(p, q);
if (major < 0) { *ok = false; return 0; }
word_t val1 = eval(p, major-1, ok);
if (!*ok) return 0;
word_t val2 = eval(major+1, q, ok);
if (!*ok) return 0;
switch(tokens[major].type) {
case '+': return val1 + val2;
case '-': return val1 - val2;
case '*': return val1 * val2;
case '/': if (val2 == 0) { *ok = false; return 0; }
return (sword_t)val1 / (sword_t)val2;
default: assert(0);
}
}
Funciones auxiliares:
bool check_parentheses(int p, int q) {
if (tokens[p].type != '(' || tokens[q].type != ')') return false;
int par = 0;
for (int i = p; i <= q; i++) {
if (tokens[i].type == '(') par++;
else if (tokens[i].type == ')') par--;
if (par == 0) return i == q;
}
return false;
}
int find_major(int p, int q) {
int ret = -1, par = 0, op_prior = 0;
for (int i = p; i <= q; i++) {
if (tokens[i].type == '(') { par++; continue; }
if (tokens[i].type == ')') {
if (par == 0) return -1;
par--;
continue;
}
if (par > 0) continue;
if (tokens[i].type == TK_NUM) continue;
int prior = 0;
switch (tokens[i].type) {
case '*': case '/': prior = 1; break;
case '+': case '-': prior = 2; break;
default: assert(0);
}
if (prior >= op_prior) { op_prior = prior; ret = i; }
}
if (par != 0) return -1;
return ret;
}
Pruebas automáticas
Se genera un programa gen-expr.c que produce 10000 expresiones aleatorias, las compila con gcc -Wall -Werror para filtrar división por cero, y compara el resultado con el obteenido por el evaluador.
static char *buf_ptr = buf;
static char *buf_lim = buf + sizeof(buf);
static int elegir(int n) { return rand() % n; }
static void gen_espacio() {
int tam = elegir(4);
if (buf_ptr < buf_lim) {
int n = snprintf(buf_ptr, buf_lim - buf_ptr, "%*s", tam, "");
if (n > 0) buf_ptr += n;
}
}
static void gen_num() {
int num = elegir(INT8_MAX);
if (buf_ptr < buf_lim) {
int n = snprintf(buf_ptr, buf_lim - buf_ptr, "%d", num);
if (n > 0) buf_ptr += n;
}
gen_espacio();
}
static void gen_char(char c) {
int n = snprintf(buf_ptr, buf_lim - buf_ptr, "%c", c);
if (n > 0) buf_ptr += n;
}
static char ops[] = {'+', '-', '*', '/'};
static void gen_operador() {
gen_char(ops[elegir(4)]);
}
static void gen_expr_aleatoria() {
switch (elegir(3)) {
case 0: gen_num(); break;
case 1: gen_char('('); gen_expr_aleatoria(); gen_char(')'); break;
default: gen_expr_aleatoria(); gen_operador(); gen_expr_aleatoria(); break;
}
}
int main(int argc, char *argv[]) {
srand(time(0));
int n_casos = (argc > 1) ? atoi(argv[1]) : 1;
for (int i = 0; i < n_casos; i++) {
buf_ptr = buf;
gen_expr_aleatoria();
sprintf(code_buf, code_format, buf);
FILE *f = fopen("/tmp/.code.c", "w");
fputs(code_buf, f); fclose(f);
if (system("gcc /tmp/.code.c -Wall -Werror -o /tmp/.expr") != 0) continue;
f = popen("/tmp/.expr", "r");
uint64_t resultado;
fscanf(f, "%lu", &resultado);
pclose(f);
printf("%lu %s\n", resultado, buf);
}
return 0;
}
La función de prueba se inserta en init_sdb:
void test_expr() {
FILE *fp = fopen("tools/gen-expr/input", "r");
if (!fp) { perror("test_expr error"); return; }
word_t correcto;
char *linea = NULL;
size_t len = 0;
bool ok;
while (fscanf(fp, "%lu ", &correcto) != -1) {
getline(&linea, &len, fp);
linea[strlen(linea)-1] = '\0';
word_t obtenido = expr(linea, &ok);
assert(ok);
if (obtenido != correcto) {
printf("Fallo: %s, esperado %lu, obtenido %lu\n", linea, correcto, obtenido);
assert(0);
}
}
fclose(fp);
free(linea);
Log("Prueba de expresiones superada");
}
void init_sdb() {
init_regex();
test_expr();
init_wp_pool();
}
Puntos de vigilancia: w EXPR / d N
Extensión del evaluador de expresiones
Se añaden operadores unarios (* como desreferencia, + y - unarios), operadores relacionales y lógicos (==, !=, <, >, <=, >=, &&, ||) y soporte para registros.
enum {
TK_NOTYPE = 256,
TK_POS, TK_NEG, TK_DEREF,
TK_EQ, TK_NEQ, TK_GT, TK_LT, TK_GE, TK_LE,
TK_AND, TK_OR,
TK_NUM, TK_REG,
};
static struct rule rules[] = {
{" +", TK_NOTYPE},
{"\\(", '('}, {"\\)", ')'},
{"\\*", '*'}, {"/", '/'},
{"\\+", '+'}, {"-", '-'},
{"<", TK_LT}, {">", TK_GT}, {"<=", TK_LE}, {">=", TK_GE},
{"==", TK_EQ}, {"!=", TK_NEQ},
{"&&", TK_AND},
{"\\|\\|", TK_OR},
{"(0x)?[0-9]+", TK_NUM},
{"\\$\\w+", TK_REG},
};
En make_token se detectan operadores unarios según el contexto:
case '*': case '-': case '+':
if (nr_token==0 || !es_tipo_limite(tokens[nr_token-1].type, ...)) {
if (rules[i].token_type == '-') tokens[nr_token].type = TK_NEG;
else if (rules[i].token_type == '+') tokens[nr_token].type = TK_POS;
else tokens[nr_token].type = TK_DEREF;
}
Actualización de eval:
static word_t eval(int p, int q, bool *ok) {
*ok = true;
if (p > q) { *ok = false; return 0; }
if (p == q) return eval_operando(p, ok);
if (check_parentheses(p, q)) return eval(p+1, q-1, ok);
int major = find_major(p, q);
if (major < 0) { *ok = false; return 0; }
bool ok1, ok2;
word_t val1 = eval(p, major-1, &ok1);
word_t val2 = eval(major+1, q, &ok2);
if (!ok2) { *ok = false; return 0; }
if (ok1) return calc2(val1, tokens[major].type, val2, ok);
else return calc1(tokens[major].type, val2, ok);
}
Funciones nuevas:
static word_t eval_operando(int i, bool *ok) {
if (tokens[i].type == TK_NUM) {
if (strncmp("0x", tokens[i].str, 2) == 0)
return strtol(tokens[i].str, NULL, 16);
else
return strtol(tokens[i].str, NULL, 10);
} else if (tokens[i].type == TK_REG) {
return isa_reg_str2val(tokens[i].str, ok);
} else {
*ok = false;
return 0;
}
}
static word_t calc1(int op, word_t val, bool *ok) {
switch (op) {
case TK_NEG: return -val;
case TK_POS: return val;
case TK_DEREF: return vaddr_read(val, 8);
default: *ok = false; return 0;
}
}
static word_t calc2(word_t val1, int op, word_t val2, bool *ok) {
switch(op) {
case '+': return val1 + val2;
case '-': return val1 - val2;
case '*': return val1 * val2;
case '/': if (val2 == 0) { *ok = false; return 0; }
return (sword_t)val1 / (sword_t)val2;
case TK_AND: return val1 && val2;
case TK_OR: return val1 || val2;
case TK_EQ: return val1 == val2;
case TK_NEQ: return val1 != val2;
case TK_GT: return val1 > val2;
case TK_LT: return val1 < val2;
case TK_GE: return val1 >= val2;
case TK_LE: return val1 <= val2;
default: *ok = false; return 0;
}
}
Gestión del pool de puntos de vigilancia
static WP* nuevo_wp() {
assert(free_);
WP* ret = free_;
free_ = free_->next;
ret->next = head;
head = ret;
return ret;
}
static void liberar_wp(WP *wp) {
WP* h = head;
if (h == wp) head = NULL;
else {
while (h && h->next != wp) h = h->next;
assert(h);
h->next = wp->next;
}
wp->next = free_;
free_ = wp;
}
Comandos w, d y mejora de info w
{ "w", "Uso: w EXPR. Vigila cambios de EXPR", cmd_w },
{ "d", "Uso: d N. Elimina el punto de vigilancia N", cmd_d },
static int cmd_info(char *args) {
char *arg = strtok(NULL, " ");
if (!arg) printf("Uso: info r (registros) o info w (puntos de vigilancia)\n");
else if (strcmp(arg, "r") == 0) isa_reg_display();
else if (strcmp(arg, "w") == 0) iterar_wp();
else printf("Uso: info r (registros) o info w (puntos de vigilancia)\n");
return 0;
}
static int cmd_w(char* args) {
if (!args) { printf("Uso: w EXPR\n"); return 0; }
bool ok;
word_t res = expr(args, &ok);
if (!ok) { puts("expresión inválida"); return 0; }
vigilar(args, res);
return 0;
}
static int cmd_d(char* args) {
char *arg = strtok(NULL, "");
if (!arg) { printf("Uso: d N\n"); return 0; }
int num = strtol(arg, NULL, 10);
eliminar_wp(num);
return 0;
}
Implementación de las funciones asociadas:
void vigilar(char *expr, word_t res) {
WP* wp = nuevo_wp();
strcpy(wp->expr, expr);
wp->old = res;
printf("Punto de vigilancia %d: %s\n", wp->NO, expr);
}
void eliminar_wp(int num) {
assert(num < NR_WP);
WP* wp = &wp_pool[num];
liberar_wp(wp);
printf("Punto de vigilancia %d eliminado: %s\n", wp->NO, wp->expr);
}
void iterar_wp() {
WP* h = head;
if (!h) { puts("No hay puntos de vigilancia."); return; }
printf("%-8s%-8s\n", "Num", "Qué");
while (h) {
printf("%-8d%-8s\n", h->NO, h->expr);
h = h->next;
}
}
En trace_and_difftest se añade la comprobación:
static void trace_and_difftest(Decode *_this, vaddr_t dnpc) {
...
IFDEF(CONFIG_WATCHPOINT, verificar_wp());
}
void verificar_wp() {
WP* h = head;
while (h) {
bool _;
word_t nuevo = expr(h->expr, &_);
if (h->old != nuevo) {
printf("Punto de vigilancia %d: %s\n"
"Valor antiguo = %lu\n"
"Valor nuevo = %lu\n",
h->NO, h->expr, h->old, nuevo);
h->old = nuevo;
}
h = h->next;
}
}
Con esto finaliza la implementación del depurador básico para PA1.