0

I just started learning Assembly and my friend and I got different ideas about what exactly happens in the following command:

Push 1234h

Our problem is: where will the first digits (12) will go to in the stack ?
ss:[sp-2] ?
ss:[sp-4] ?

*sp =stack pointer

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • By the way, you could figure this out by yourself by writing `push 1234h / mov al,[sp]`, and then examine the value of `al`, then `mov al,[sp-1]` again. – Jim Mischel Jul 12 '13 at 13:33
  • Related: [How many bytes does the push instruction push onto the stack when I don't specify the operand size?](https://stackoverflow.com/q/45127993) re: how `push 1234` assembles. Most assemblers pick an operand-size that matches the mode they're assembling for. (e.g. 64 for `bits 64`) – Peter Cordes Jan 16 '23 at 11:02

3 Answers3

3

Assuming you're talking about x86 hardware (since you specify the sp register), the bytes will be pushed highest-order (most significant) to lowest-order (least significant). In this case, it'll push the byte 12h, followed by the byte 34h. Since the x86 stack pointer decreases when you push items, the memory layout would look like this:

[sp+1] = 12h
[sp]   = 34h

If you access [sp] as a word (two bytes), you'll get your original value:

[sp] = 1234h
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Drew McGowen
  • 11,471
  • 1
  • 31
  • 57
0

Believe it or not but both of you can be right. The bytes ordering depends on the endianness of your machine.

SomeWittyUsername
  • 18,025
  • 3
  • 42
  • 85
  • x86 is *always* little-endian, so there's only one possibility for endianness. Also, the question is really whether the default operand-size is 16 or 32. Both are possible depending on the current mode you're assembling for, and thus whether the assembler uses `push dword imm32` or `push word imm16` if you don't override the size. [How many bytes does the push instruction push onto the stack when I don't specify the operand size?](https://stackoverflow.com/q/45127993) – Peter Cordes Jan 08 '23 at 01:42
0

In real mode (it looks like you're trying to make programs for it), address is calculated first. The formula is pretty simple:

address = (segment<<4)+offset

Then this address is substracted by size of element you're trying to push (word: 2 bytes, dword: 4 bytes, qword: 8 bytes). Third step is writing data on resulting position. Therefore if SS is 0x30 and SP 0xFF, then:

  1. address = (0x30<<4)+0xFF = 0x3FF
  2. Element is word, so we substract 2: position = 0x3FF - 2 = 0x3FD
  3. Writing data on resulting position (as C pointers): *(unsigned char*)position = LOBYTE(0x1234), *((unsigned char*)position+1) = HIBYTE(0x1234).

So, byte SS:[SP] = 0x34 and byte SS:[SP+1] = 0x12. Note that stack grows/expands down, so there won't be SP-x, but SP+x.

In protected mode, things are bit more complicated (I'll write here only process of working with segment register):

  1. First, processor get's address of Global Descriptor Table Descriptor (which is loaded by LGDT instruction).

  2. Processor compares value of segment register with size of GDT (if it's greater, exception is thrown). It also checks if segment register isn't pointing to null descriptor (SS!=0)

  3. Then processor gets pointer to GDT entries, and calculates offset from start of GDT (using SS*8).
  4. Processor must check some things:

    4.1. Is segment present? 4.2. Do we have enough priviliges to access segment? 4.3. Is it code segment? 4.4. Is it writable? 4.5. Is ESP*granularity+base < limit 4.6. Is it system segment?

  5. Then it calculates address by base_of_segment+ESP.

  6. After it it continues similary to real mode.

user35443
  • 6,309
  • 12
  • 52
  • 75