Strange behavior of variable values within procedures

Bare metal programming in PureBasic, for experienced users
dangerfreak
User
User
Posts: 32
Joined: Tue Jan 12, 2010 4:56 pm

Strange behavior of variable values within procedures

Post by dangerfreak »

Today I played around with some inline assembler code within a purebasic procedure (code see below). And there something strange happened, at least I think it is strange, there sure is an explanation for it (I just don't know it :D ).

I defined some global and some local purebasic variables (local = inside a procedure). Then I went to ASM-mode and made a CALL to an assembly subroutine. Inside this subroutine, I checked the values of the purebasic variables - they were all wrong (shifted, see example below).

I know, if I do a CALL, the address of it is pushed on the stack, so that the subroutine can return to the call (after the call). So my next test was to get this address from the stack (POP). After that, I checked the values of the purebasic variables again - and guess what: They were all correct now!

I made the same test with global variables and outside a procedure, so i called my subroutine and checked the purebasic variables. They were all correct.

Now my question: Why is there a difference between inside and outside a procedure? Doesn't make sense for me.


Code for 64 bit OS (Windows 10):

Code: Select all

EnableExplicit

; Some really BAD code on Windows 10, 64 bit, don't blame me for this crap ;-)
; Assembly test: CALL within a procedure

Declare main()

Global *jumpBackTo
Global iAmGlobal1.l=5
Global iAmGlobal2.l=6
Global iAmGlobal3.l=7

Debug("Global test:")
EnableASM
CALL subroutineGlobal
JMP gameoverGlobal

!subroutineGlobal:
Debug("Values of iAmGlobal1-3 before POP")
Debug(iAmGlobal1)
Debug(iAmGlobal2)
Debug(iAmGlobal3)
POP rax
MOV *jumpBackTo,rax
Debug("Values of iAmGlobal1-3 after POP")
Debug(iAmGlobal1)
Debug(iAmGlobal2)
Debug(iAmGlobal3)
Debug(">>> IDENTICAL :-)")
JMP *jumpBackTo

!gameoverGlobal:
DisableASM
Debug("-----End of global test---------")

main()
End

; -----------Procedures---------------------------

Procedure main()
   Define iAmLocal1.l=1
   Define iAmLocal2.l=2
   Define iAmLocal3.l=3
   
   Debug("Local test")
   EnableASM
   CALL subroutineSub
   JMP gameoverSub
   
   !subroutineSub:
   Debug("Values of iAmLocal1-3 and iAmGlobal1 before POP")
   Debug(iAmLocal1)
   Debug(iAmLocal2)
   Debug(iAmLocal3)
   Debug(iAmGlobal1)
   Debug(">>> Huh?!?")
   POP rax
   MOV *jumpBackTo,rax
   Debug("Values of iAmLocal1-3 and iAmGlobal1 after POP")
   Debug(iAmLocal1)
   Debug(iAmLocal2)
   Debug(iAmLocal3)
   Debug(iAmGlobal1)
   Debug(">>> Muuuuuch better! But why?")
   JMP *jumpBackTo
   
   !gameoverSub:
   DisableASM
   Debug("-----End of local test---------")
EndProcedure

DEBUGGER OUTPUT:
Global test:
Values of iAmGlobal1-3 before POP
5
6
7
Values of iAmGlobal1-3 after POP
5
6
7
>>> IDENTICAL :-)
-----End of global test---------
Local test
Values of iAmLocal1-3 and iAmGlobal1 before POP
0
1
2
5
>>> Huh?!?
Values of iAmLocal1-3 and iAmGlobal1 after POP
1
2
3
5
>>> Muuuuuch better! But why?
-----End of local test---------
User avatar
STARGÅTE
Addict
Addict
Posts: 2067
Joined: Thu Jan 10, 2008 1:30 pm
Location: Germany, Glienicke
Contact:

Re: Strange behavior of variable values within procedures

Post by STARGÅTE »

First of all, it is a bad idea to mix ASM with PB code (especially Debug).
The reason is, the PB functions can/will change the registers and the stack.

Secondly, global variables are stored in memory, whereas local variables in a procedure are stored in the stack!
Therefore iAmGlobal1 to iAmGlobal3 are not affected by your POP rax, whereas iAmLocal1 to iAmLocal3 are in the stack and POP rax changes the position.
Further CALL subroutineSub pushes the value of the EIP register, therefore the values of iAmLocal1 to iAmLocal3 are wrong before you pop this value.

Edit: Btw. you need a RET command after you jump with CALL.
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Lizard - Script language for symbolic calculations and moreTypeface - Sprite-based font include/module
User avatar
spikey
Enthusiast
Enthusiast
Posts: 581
Joined: Wed Sep 22, 2010 1:17 pm
Location: United Kingdom

Re: Strange behavior of variable values within procedures

Post by spikey »

STARGÅTE wrote: Thu Dec 01, 2022 8:11 pm Edit: Btw. you need a RET command after you jump with CALL.
Actually that's not entirely accurate. If you just add a RET to this program a second stack related error will arise, probably a bigger one. This is because the stack pointer is being manually adjusted and ends up where it should have been in the first place. If you were to then RET the instruction pointer will probably end up pointing somewhere inappropriate. It's more accurate to say that you should generally balance CALLs with RETs and shouldn't fiddle with the stack pointer. (Unless you like crashes of course :D ).
juergenkulow
Enthusiast
Enthusiast
Posts: 544
Joined: Wed Sep 25, 2019 10:18 am

Re: Strange behavior of variable values within procedures

Post by juergenkulow »

Have a lot of fun with x64dbg.

Code: Select all

0000000140001000 | 48:83EC 28               | sub rsp,28                                                    |
0000000140001004 | 49:C7C0 38000000         | mov r8,38                                                     | 38:'8'
000000014000100B | 48:31D2                  | xor rdx,rdx                                                   | rdx:"Hƒì(IÇÀ8"
000000014000100E | 48:B9 1431004001000000   | mov rcx,call.140003114                                        |
0000000140001018 | E8 E30F0000              | call <JMP.&memset>                                            |
000000014000101D | 48:31C9                  | xor rcx,rcx                                                   |
0000000140001020 | E8 E10F0000              | call <JMP.&GetModuleHandleW>                                  |
0000000140001025 | 48:8905 F0200000         | mov qword ptr ds:[14000311C],rax                              |
000000014000102C | 4D:31C0                  | xor r8,r8                                                     |
000000014000102F | 48:C7C2 00100000         | mov rdx,1000                                                  | rdx:"Hƒì(IÇÀ8"
0000000140001036 | 48:31C9                  | xor rcx,rcx                                                   |
0000000140001039 | E8 CE0F0000              | call <JMP.&HeapCreate>                                        |
000000014000103E | 48:8905 CF200000         | mov qword ptr ds:[140003114],rax                              |
0000000140001045 | C705 ED200000 05000000   | mov dword ptr ds:[14000313C],5                                |
000000014000104F | C705 E7200000 06000000   | mov dword ptr ds:[140003140],6                                |
0000000140001059 | C705 E1200000 07000000   | mov dword ptr ds:[140003144],7                                |
0000000140001063 | E8 02000000              | call call.14000106A                                           |
0000000140001068 | EB 0E                    | jmp call.140001078                                            |
000000014000106A | 58                       | pop rax                                                       |
000000014000106B | 48:8905 C2200000         | mov qword ptr ds:[140003134],rax                              |
0000000140001072 | FF25 BC200000            | jmp qword ptr ds:[140003134]                                  |
0000000140001078 | E8 28000000              | call call.1400010A5                                           |
000000014000107D | EB 00                    | jmp call.14000107F                                            |
000000014000107F | E8 18000000              | call call.14000109C                                           |
0000000140001084 | 48:8B0D 89200000         | mov rcx,qword ptr ds:[140003114]                              |
000000014000108B | E8 820F0000              | call <JMP.&HeapDestroy>                                       |
0000000140001090 | 48:8B0D 8D200000         | mov rcx,qword ptr ds:[140003124]                              |
0000000140001097 | E8 7C0F0000              | call <JMP.&RtlExitUserProcess>                                |
000000014000109C | 48:83EC 28               | sub rsp,28                                                    |
00000001400010A0 | 48:83C4 28               | add rsp,28                                                    |
00000001400010A4 | C3                       | ret                                                           |
00000001400010A5 | 48:31C0                  | xor rax,rax                                                   |
00000001400010A8 | 50                       | push rax                                                      |
00000001400010A9 | 50                       | push rax                                                      |
00000001400010AA | 50                       | push rax                                                      |
00000001400010AB | 50                       | push rax                                                      |
00000001400010AC | 48:83EC 28               | sub rsp,28                                                    |
00000001400010B0 | C74424 28 01000000       | mov dword ptr ss:[rsp+28],1                                   |
00000001400010B8 | C74424 30 02000000       | mov dword ptr ss:[rsp+30],2                                   |
00000001400010C0 | C74424 38 03000000       | mov dword ptr ss:[rsp+38],3                                   |
00000001400010C8 | E8 02000000              | call call.1400010CF                                           |
00000001400010CD | EB 0E                    | jmp call.1400010DD                                            |
00000001400010CF | 58                       | pop rax                                                       |
00000001400010D0 | 48:8905 5D200000         | mov qword ptr ds:[140003134],rax                              |
00000001400010D7 | FF25 57200000            | jmp qword ptr ds:[140003134]                                  |
00000001400010DD | 48:31C0                  | xor rax,rax                                                   |
00000001400010E0 | 48:83C4 48               | add rsp,48                                                    |
00000001400010E4 | C3                       | ret                                                           |

Code: Select all

{p:cip}            {rsp}  {i:cip}
0x0000000140001045 12FF30 mov dword ptr ds:[0x000000014000313C], 0x5
0x000000014000104F 12FF30 mov dword ptr ds:[0x0000000140003140], 0x6
0x0000000140001059 12FF30 mov dword ptr ds:[0x0000000140003144], 0x7
0x0000000140001063 12FF30 call 0x000000014000106A
0x000000014000106A 12FF28 pop rax
0x000000014000106B 12FF30 mov qword ptr ds:[0x0000000140003134], rax
0x0000000140001072 12FF30 jmp qword ptr ds:[0x0000000140003134]
0x0000000140001068 12FF30 jmp 0x0000000140001078
0x0000000140001078 12FF30 call 0x00000001400010A5
0x00000001400010A5 12FF28 xor rax, rax
0x00000001400010A8 12FF28 push rax
0x00000001400010A9 12FF20 push rax
0x00000001400010AA 12FF18 push rax
0x00000001400010AB 12FF10 push rax
0x00000001400010AC 12FF08 sub rsp, 0x28
0x00000001400010B0 12FEE0 mov dword ptr ss:[rsp+0x28], 0x1
0x00000001400010B8 12FEE0 mov dword ptr ss:[rsp+0x30], 0x2
0x00000001400010C0 12FEE0 mov dword ptr ss:[rsp+0x38], 0x3
0x00000001400010C8 12FEE0 call 0x00000001400010CF
0x00000001400010CF 12FED8 pop rax
0x00000001400010D0 12FEE0 mov qword ptr ds:[0x0000000140003134], rax
0x00000001400010D7 12FEE0 jmp qword ptr ds:[0x0000000140003134]
0x00000001400010CD 12FEE0 jmp 0x00000001400010DD
0x00000001400010DD 12FEE0 xor rax, rax
0x00000001400010E0 12FEE0 add rsp, 0x48
0x00000001400010E4 12FF28 ret
0x000000014000107D 12FF30 jmp 0x000000014000107F
0x000000014000107F 12FF30 call 0x000000014000109C
0x000000014000109C 12FF28 sub rsp, 0x28
0x00000001400010A0 12FF00 add rsp, 0x28
0x00000001400010A4 12FF28 ret
Edit: CPU-Code and Trace
Last edited by juergenkulow on Sat Dec 03, 2022 3:41 pm, edited 1 time in total.
dangerfreak
User
User
Posts: 32
Joined: Tue Jan 12, 2010 4:56 pm

Re: Strange behavior of variable values within procedures

Post by dangerfreak »

Thanks a lot @STARGÅTE for explaining this little mystery. Now I clearly understand, what happens using assembly within procedures.
(I know that using PB functions can change registers and stack, it was just to show my problem)

Also thanks @spikey , I didn't use a RET with intent, because I already took away the return address from the stack. So I had to use a JMP or restore the stack (PUSH *jumpBackTo) and then use a RET. Probably a bad coding style, but it worked :D .
Post Reply