* * * * *
A quick dip back into assembly with some curious results …
Speaking of assembly [1] …
One of the instructions of the x86 architecture [2] I've been curious about
is ENTER [3]. Oh, I know it's there to support higher level languages like C
and Pascal that use stack frames for local variables. It even supposedly
supports nested function definitions (ala Pascal) using the second operand as
a kind of “nesting level.”
But I've never seen an actual instance of ENTER used with a “nesting level”
greater than 0. The only instance I've ever seen used has been
> ENTER n,0
>
Which is equivilent to
> PUSH EBP ; or BP if 16-bit code
> MOV EBP,ESP
> SUB ESP,n
>
(And in fact, that sequence is generated by GCC (GNU Compiler Collection) [4]
as it's actually faster than ENTER n,0 and C doesn't allow nested functions
to begin with.)
But being curious about what ENTER actually does, I decided to play around
with it. I wrote some simple code:
> bits 32
> global sub0
> extern pmem
>
> section .text
>
> sub0 enter 8,0
> mov eax,0DEADBEEFh
> mov [ebp-4],eax
> mov eax,0CAFEBABEh
> mov [ebp-8],eax
> lea ebx,[ebp+4]
> push dword 0c0000001h
> call sub1
> leave
> ret
>
> sub1 enter 8,1
> mov eax,0DEADBEEFh
> mov [ebp-4],eax
> mov eax,0CAFEBABEh
> mov [ebp-8],eax
> push dword 0c0000002h
> call sub2
> leave
> ret
>
> sub2 enter 8,2
> mov eax,0DEADBEEFh
> mov [ebp-4],eax
> mov eax,0CAFEBABEh
> mov [ebp-8],eax
> push dword 0c0000003h
> call sub3
> leave
> ret
>
> sub3 enter 8,3
> mov eax,0DEADBEEFh
> mov [ebp-4],eax
> mov eax,0CAFEBABEh
> mov [ebp-8],eax
> push dword 0c0000004h
> call sub4
> leave
> ret
>
> sub4 enter 8,4
> mov eax,0DEADBEEFh
> mov [ebp-4],eax
> mov eax,0CAFEBABEh
> mov [ebp-8],eax
> push dword 0
> push dword 0
> push ebx
> push esp
> call pmem
> add esp,16
> leave
> ret
>
>
And the following C code:
> #include <stdio.h>
> #include <stdlib.h>
>
> extern void sub0(void);
>
> void pmem(unsigned long *pl,unsigned long *ph)
> {
> assert(pl < ph);
>
> while(ph >= pl - 2)
> {
> printf("\t%08lX: %08lX\n",(unsigned long)ph,*ph);
> ph--;
> }
> }
>
> int main(void)
> {
> sub0();
> return EXIT_SUCCESS;
> }
>
>
Nothing horribly complicated here. pmem() just dumps the stack, and the
various sub*() routines create deeper nestings of stack activation records
while creating enough space to store two four-byte values. The results
though?
Curious (comments added by me after the run) …
> BFFFFD1C: 0804853C return addr to main()
> BFFFFD18: BFFFFD20 stack frame sub0
> BFFFFD14: DEADBEEF local0
> BFFFFD10: CAFEBABE local1
> BFFFFD0C: C0000001 marker for calling sub1
> BFFFFD08: 08048591 return addr to sub0
> BFFFFD04: BFFFFD18 stack frame sub1
> BFFFFD00: DEADBEEF local0
> BFFFFCFC: CAFEBABE local1
> BFFFFCF8: 08049708 ?
> BFFFFCF4: C0000002 marker for calling sub2
> BFFFFCF0: 080485B1 return addr to sub1
> BFFFFCEC: BFFFFD04 stack frame sub2
> BFFFFCE8: DEADBEEF local0
> BFFFFCE4: CAFEBABE local1
> BFFFFCE0: 00000002 ?
> BFFFFCDC: 400079D4 ?
> BFFFFCD8: C0000003 marker for calling sub3
> BFFFFCD4: 080485D1 return addr to sub2
> BFFFFCD0: BFFFFCEC stack frame sub3
> BFFFFCCC: DEADBEEF local0
> BFFFFCC8: CAFEBABE local1
> BFFFFCC4: BFFFFCD0 ? sf3
> BFFFFCC0: 4000F000 ?
> BFFFFCBC: 02ADAE54 ?
> BFFFFCB8: C0000004 marker for calling sub4
> BFFFFCB4: 080485F1 return addr to sub3
> BFFFFCB0: BFFFFCD0 stack frame sub4
> BFFFFCAC: DEADBEEF local0
> BFFFFCA8: CAFEBABE local0
> BFFFFCA4: BFFFFCD0 ? sf3
> BFFFFCA0: BFFFFCB0 ? sf4
> BFFFFC9C: 40011FE0 ?
> BFFFFC98: 00000001 ?
> BFFFFC94: 00000000 push dword 0
> BFFFFC90: 00000000 push dword 0
> BFFFFC8C: BFFFFC8C ? supposed to be ebx
> BFFFFC88: BFFFFC8C ? supposed to be esp
> BFFFFC84: 08048618 return addr to sub4
>
From my understanding of what ENTER does, each “level” creates a type of
nested stack activation record with pointers to each previous “level's” stack
record. And while each level has the required number of additional entries,
the actual contents don't make sense.
Running this on a different Linux system produced similarly confusing
results. I'm not sure if ENTER is horribly broken these days (I wonder how
often the instruction is actually used), or perhaps, it is indeed a Linux
problem [5]? Not that I'm going to be using assembly any time soon … I'm just
curious.
[1]
gopher://gopher.conman.org/0Phlog:2008/07/08.1
[2]
http://en.wikipedia.org/wiki/X86_assembly_language
[3]
http://www.cs.ucla.edu/~kohler/class/04f-aos/ref/i386/ENTER.htm
[4]
http://gcc.gnu.org/
[5]
http://groups.google.co.nz/group/comp.os.linux.development.system/browse_thread/thread/a057249198598933/a4f5251c9ef1e7a2?#a4f5251c9ef1e7a2
Email author at
[email protected]