aa corrupts known main prototype on x86-32, drops argc and shifts argv/envp
Environment
$ date
r2 -v
uname -ms
Fri 18 Sep 01:14:14 BST 2026
radare2 6.2.2 +37545 abi:142 @ linux-x86_64
birth: git.6.2.2 2026-09-17__17:41:32
commit: ad27058877024389292fddf12e1db6e13824ba34
options: gpl -O? cs:5 cl:2 make
Linux x86_64Description
aa corrupts an already-known main prototype on x86-32.
Before analysis, radare2 has the expected prototype metadata:
[0x00001060]> afcf main
[0x00001060]> tk func.main.args
3
[0x00001060]> tk func.main.arg.0
int,argc
[0x00001060]> tk func.main.arg.1
char **,argv
[0x00001060]> tk func.main.arg.2
char **,envpAfter aa, it drops argc and shifts argv and envp one stack word earlier:
[0x00001060]> aa
INFO: Analyze all flags starting with sym. and entry0 (aa)
INFO: Analyze imports (af@@@i)
INFO: Name local plt stubs from their got relocs (anal.plt)
INFO: Analyze entrypoint (af@ entry0)
INFO: Analyze symbols (af@@@s)
INFO: Recovering variables (afva@@F)
INFO: Running plugin pre-analysis hooks
INFO: Analyze all functions arguments/locals (afva@@F)
[0x00001060]> s sym.main
[0x0000118d]> afcf main
int main (char **argv, char **envp);
[0x0000118d]> tk func.main.args
2
[0x0000118d]> tk func.main.arg.0
char **,argv
[0x0000118d]> tk func.main.arg.1
char **,envp
[0x0000118d]> tk func.main.arg.2
[0x0000118d]> afvb
arg char ** argv @ ebp+0x8
arg char ** envp @ ebp+0xcFor this standard 32-bit frame, the known ABI prototype requires:
int main (int argc, char **argv, char **envp);
arg int argc @ ebp+0x8
arg char ** argv @ ebp+0xc
arg char ** envp @ ebp+0x10The recovered locations are therefore associated with prototype arguments 1 and 2 rather than 0 and 1. This looks like an off-by-one mapping between recovered BP-relative argument slots and the pre-existing prototype.
The explicit afva path has a guard that preserves a function with known type metadata:
case 'a': // "afva"
if (fcn) {
char *type = r_str_newf ("func.%s.ret", fcn->name);
if (type && sdb_exists (core->anal->sdb_types, type)) {
// if function type exists
// do not analize vars if function has a signature
} else {
r_anal_function_delete_all_vars (fcn);
r_core_recover_vars (core, fcn, false);
}
...
}cmd_aa() instead calls r_core_recover_vars(core, fcni, true) directly for every function:
if (anal_vars) {
logline (core, 22, "Recovering variables (afva@@F)");
r_list_foreach_prev (core->anal->fcns, iter, fcni) {
...
r_core_recover_vars (core, fcni, true);
...
}
}Could this bulk recovery path honor the same signature-preservation rule, or otherwise preserve the known prototype's argument-slot mapping?
Reproducer
int main(int argc, char **argv)
{
return argc + !!argv;
}gcc -m32 -O0 -fno-omit-frame-pointer test.c -o test
r2 ./testThen run the commands in the Description section.
Expected result
aa must not replace or reinterpret arguments from the already-known func.main.* prototype. The final prototype and BP-relative variables should remain:
int main (int argc, char **argv, char **envp);
arg int argc @ ebp+0x8
arg char ** argv @ ebp+0xc
arg char ** envp @ ebp+0x10Actual result
aa replaces the known three-argument prototype with:
int main (char **argv, char **envp);
arg char ** argv @ ebp+0x8
arg char ** envp @ ebp+0xcSource: radareorg/radare2