Search code examples
cassemblyoperating-systemexecutableosdev

Why does the "push" instruction give me an "invalid opcode" exception? (osdev)


What I'm Doing & What Works

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.

The problem

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.)


Solution

  • 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.