Software Reverse Engineering is one of my hobby.
First things first : forget about headers. all information about headers and separate C file are gone.
You're missing some crucial step, imho.
- Compilation create one or multiple object file (.o) then the linker create an executable.
- You should work from disassembled code. Disassembler works pretty well with some exception (self modifying code, self extracting executable, various obfuscation techniques) and will take care of a lot of work for you : identifying various sections, finding functions, guessing (fairly accurately) the calling convention.
- then compiler optimization will mess up your code in a very clever way and some part of your original code will never ever be seen again (hey, look, your 200 lines of bugged code always return 0 anyway so i'll just replace them with "xor eax, eax").
- sometimes it's fine, and sometimes it produce unreadable C code (vectorization that have no C equivalent and will be decompiled into hundreds of line of intrinsic instead of a fine readable "for" loop).
- i'm not done yet. you also have exception and interrupts, structures, union, function pointers, function inlining, threading, system call and signals, loop unrolling, ...
going down (from human readable to binary) is fairly easy compared to going up (decompilation) because so much information is lost during the compilation process.
my best bet would be to have a bunch of disassembled function produced by a disassember and produce an LLVM intermediate representation using your IA, then compare it with the LLVM IR produced by Clang (clang -S -emit-llvm foo.c).
An infinite quantity of C code can produce the exact same code. Therefore, i think it's meaningless to make an AI read C code for the purpose of decompilation : the information is lost forever.
Commercial and Open/Free decompiler do not produce C code either, they produce some kind of pseudo-C full of error, missing code, or code even less readable than the ASM.
The following code :
int main() {
int toto = 0x0000BEEF;
int titi = 0xDEAD0000;
toto = toto | titi;
return toto;
}
produce this :
int __cdecl main(int argc, char** argv, char** envp) {
return 0xDEADBEEF
}
you can't go back and you have no way of knowing this is the exact same code unless 1) you can do static analysis (very easy in this case but absurdly difficult in the real world) 2) or compare the IR or ASM produced by both code with the same compiler with the same options on the same architecture and operating system.