I've been working on my own 32 bit toy kernel in C for a while, and lately I've been fiddling around with executing dynamically defined code, as I'd like to add a binary loader soon.
In particular what I'm doing for now is creating char arrays containing the opcodes of a series of instructions, copying them to the beginning of a new (executable) page and appending the opcode of a ret
instruction, e.g.:
char div_by_zero [] = { 0xba, 0x00, 0x00, 0x00, 0x00, // mov 0x0, edx
0xb8, 0xfa, 0x00, 0x00, 0x00, // mov 0xfa, eax
0xb9, 0x00, 0x00, 0x00, 0x00, // mov 0x0, ecx
0xf7, 0xf1 // div ecx ---> DIV BY 0
};
void exec_test(){
char* thing = (char*) get_virtual_address(0,1, 0);
char postamble [] = {0xc3}; // ret
char *code = div_by_zero;
/* [...] omitted: copying to 'thing' and adding the 'postamble' at the end... */
__asm__("call %0" :: "m" (thing)); // Starts executing the "string" (lol)
}
This one was the first one I tried, which as expected resulted in a DIVISION BY ZERO
.
Then I tried the following:
// movb $0x41,0x1000
char write_A_on_1000 [] = { 0xc6, 0x05, 0x00, 0x10, 0x00, 0x00, 0x41 };
And then printed the character at memory location 0x1000
, which as expected was a capital A
.
Another one I tried, seemingly quite simple, was the following:
char push_one [] = {0x6a, 0x01};
Which should simply push the integer 1
on the stack. Unlike the other examples, this one does not seem to work. In fact, it gives me an INVALID OPCODE
exception.
One of the things I tried to do was to write the push
explicitly, by subtracting 4 to the stack pointer etc (which also resulted in an INVALID OPCODE
), but then again, my future loader should be able to execute push
just fine, so I should really fix this..
(Btw, I'm targeting i386
machines and currently using QEMU to run the kernel.)
Recall that the return address is stored on the stack. If you push something on the stack and then return, you'll return to whatever address you pushed on the stack. This is unlikely to go well.
Make sure to keep the stack balanced when performing experiments like this. For example, you could pop off the value you just pushed:
/* push $1; pop %eax */
char push_one [] = {0x6a, 0x01, 0x58};
You can observe this by investigating the return value, which the compiler expects to be in eax
.