[NASM] Procedura wczytująca liczbę

0

Witam,
Mam problem z napisaniem procedury wykonującej: scanf("%d", EAX). Oczywiście w języku C zamiast EAX podaje się adres zmiennej w pamięci.

Wstępnie napisałem taki kod:

;
; read_int: scanf("%d", EAX)
;
read_c_int:  push ebp ; set up stack frame
  mov ebp, esp

  push eax
  push dword str_int
  call scanf ; call C
  add esp, 4
  pop eax

  mov esp, ebp ; takedown stack frame
  pop ebp
ret ; return to previous block

Oczywiście nie działa.

Natomiast taka procedurka już sobie radzi, ale używa dodatkowej zmiennej:

;
; read_int: scanf("%d", EAX)
;
read_c_int:
  push ebp ; set up stack frame
  mov ebp, esp

  push a
  push dword str_int
  call scanf ; scanf("%d", &a)
  add esp, 8

  mov eax, [a]

  mov esp, ebp ; takedown stack frame
  pop ebp
ret ; return to previous block

Niestety, rozwiązanie to ma jedną wadę - używa zmiennej globalnej zadeklarowanej w sekcji .bss. Idealnie jakbym mógł użyć w tym celu zmiennej lokalnej (czy to jest możliwe?) albo bezpośrednio pisać do zawartości rejestru.

Jaki jest najlepszy sposób na rozwiązanie tego problemu typowy dla NASM?

Pozdrawiam,

0

Jak ustawiasz wartosc eax przed wywolaniem tej pierwszej wersji procki ?

0

Omfg, ludzie, t0m_k, co to za pytanie?

read_c_int:
        push    0               ; wartosc domyslna
        push    esp
        push    _format
        call    scanf
        pop     ecx
        pop     ecx             ; zdejmowanie argumentow cdecl
        pop     eax             ; zdjecie zmiennej z pobrana wartoscia
        retn
0
deus napisał(a)

t0m_k, co to za pytanie?

nie wiem, umknelo mi ze chodzi o zwracana wartosc, wiec szukalem bledu gdzie indziej <wstyd>

0

Przetestowane. Teraz próbuje zrozumieć działanie kodu.

read_c_int1:
; kod którego nie rozumiem        
        push    0               ; wartosc domyslna

; od tego miejsca kumam
        push    esp
        push    _format ; odpowiednik _str_int
        call    scanf
        pop     ecx
        pop     ecx             ; zdejmowanie argumentow cdecl
        pop     eax             ; zdjecie zmiennej z pobrana wartoscia
; instrukcja skoku inna niż ja używałem
        retn
  1. Dlaczego:
    push 0 ; wartosc domyslna

  2. Zamiast adresu zmiennej scanf otrzymalo esp (wskaźnik stosu, czyli pakujemy to co wczyta SCANF na stos). A wartość ze stosu może zostać łatwo wrzucona do EAX. I mam co chciałem.

  3. Modyfikacja mojej oryginalnej funkcji, którą lepiej rozumiem i jest bardziej efektywna niż pierwsza, niedziałająca wersja (zastąpienie add przez pop):

;
; read_int: scanf("%d", EAX)
;
read_c_int2:  
  push ebp ; set up stack frame
  mov ebp, esp

  push esp
  push dword str_int
  call scanf ; call C
  pop ecx
  pop ecx ; pop 8 bytes
  pop eax

  mov esp, ebp ; takedown stack frame
  pop ebp
ret ; return to previous block

Czy instrukcje na początku proponowanej procedury to po prostu inny sposób obsługi ramki stosu? Krótsze to, więc pewnie bardziej efektywne, idiomatyczne, ale wydaje się mniej czytelne.

W każdym razie wielkie dzięki.

0
margor napisał(a)

Czy instrukcje na początku proponowanej procedury to po prostu inny sposób obsługi ramki stosu? Krótsze to, więc pewnie bardziej efektywne, idiomatyczne, ale wydaje się mniej czytelne.

Nie jest to inny sposob ustawiania ramek. Deus po prostu dal wartosc 0 na stos, aby zapewnic sobie, ze nie nadpisze zadnych danych, a powrot instrukcja retn sluzy do krotkich powrotow. Jak chcesz krotsze ramki to zainteresuj sie rozkazami enter oraz leave ;)

0

To nienadpisywanie danych może być jedną z rzeczy jakie wcześniej były mi potrzebne - wcześniej radziłem sobie z tym odpowiednią ilościa push i pop, aby główne rejestry nie zostały zmienione (np. ECX w pętli). Muszę się temu przyjrzeć, dzięki.

0

No dobra, to krótkie wytłumaczenie. Pierwsza instrukcja wrzuca zero na stos, jako coś w charakterze zmiennej lokalnej - inta - do którego zostanie odczytana wartość. Następnie adres tej zmiennej (aktualna wartość esp) idzie jako drugi argument dla scanfa, potem jak u Ciebie. Na koniec po wyrównaniu stosu z wywołania zdejmujemy zmienną, do której trafiła wprowadzona liczba. Ramki stosu nie budowałem wcale, nigdy nie jest wymagana (chyba że, chodzi ładne generowanie callstacków pod debuggerem...), chociaż często pomaga, tutaj korzyści z niej zwyczajnie nie ma - nie ma czego adresować względem ebp, bo chyba nie tą jedną zmienną zdejmowaną popem? Retn to to samo co ret w tym wypadku, zazwyczaj przyjmuje się to jako alias - Intel w swoich manualach dla wszystkich form używa nazwy ret, jednak popularniejsze jest używanie 'pełniejszych' form - retn dla bliskiego powrotu, zmieniającego tylko wskaźnik instrukcji, oraz retf modyfikującego także wartość cs.

t0m_k-tmp napisał(a)

Jak chcesz krotsze ramki to zainteresuj sie rozkazami enter oraz leave ;)

Tja, z tym enter to jeden z lepszych żartów...

0
deus napisał(a)

Tja, z tym enter to jeden z lepszych żartów...

Nie zartowalem...

0

Nikt normalny nie używa enter. Jest cholernie wolne, nieczytelne i większość jego 'polecających' za cholerę nie rozumie tak naprawdę jak to działa. Sam Intel trzyma to wyłącznie dla kompatybilności... Generuje mniejszy kod, to fakt, ale na tym się zalety tego badziewia kończą - powstało żeby ułatwić implementację kompilatorów Fortranu, nie do normalnego użycia.

1 użytkowników online, w tym zalogowanych: 0, gości: 1