Zwracanie hard coded strings i trochę o samym return

Z pewnością kilka osób zna pewnego rodzaju sztuczkę która powoduje crash kompilatora.
Wygląda to mnie więcej tak
[pawn]public functionTest(){
return „Hard coded string”;
}
[/pawn]
( działa na localu jak i na webkompilatorze amxx.pl 😉 )

Nie ma żadnego uzasadnienia w zasadach i ograniczenia języka pawn który by nie pozwalał na tego typu kod
jest to raczej bug w kodzie kompilatora.

Trochę o samym zwracaniu wartości z funkcji.

Możemy wydzielić dwie sytuacje :
Pierwsza kiedy zwracamy wartość ( stałą , ze zmiennej , true/false etc. )
Tutaj jest prosto zwraca wartość ląduje w rejestrze PRI ( czym jest rejestr PRI pod koniec wpisu ) skąd może zostać pobrana przez funkcje wywołująca.

Dwa przykłady
[pawn]public test(){
return 1;
}

public test2(){
new var = 1;
return var;
}[/pawn]

i kod assemblerowy tych funkcji ( usunąłem informacje dla debuggera )

[pawn]proc ; test
const.pri 1
retn

proc ; test2
;$lcl var fffffffc
push.c 1
;$exp
load.s.pri fffffffc
;$exp
stack 4
retn[/pawn]

W pierwszym przypadku kod assemblerowy jest prosty
[pawn]const.pri 1[/pawn] wartość rejestru pri zostaje ustawiona na 1 tyle.
w drugiej funkcji widzimy najpierw odłożenie wartości na stos ( 1 )
potem pobranie tej wartości i zapisanie do pri [pawn]load.s.pri fffffffc[/pawn]
warto zauważyć że przy load.s.pri dostęp uzyskujemy poprzez rejestr FRM i offset.
Pod koniec czyszczony jest stos tzn. do rejestru STK zostaje dodana wartość w zależności ile zmiennych było rejestrowanych w funkcji itp. ( stack 4 )

Samo pobranie wartości return przez funkcję wygląda bardzo prosto
[pawn]public test6(){
new var;

var = test2();
}[/pawn]
[pawn]proc ; test6
;$lcl var fffffffc
push.c 0
;$exp
push.c 0
call test2
stor.s.pri fffffffc
;$exp
stack 4
zero.pri
retn[/pawn]
Najpierw następuje wrzucenie wartości na stos ( rejestracja zmiennej )
Potem wywołanie funkcji( push i call ) push oznacza ilość parametrów w tym przypadku 0
Po callu następuje pobranie wartości i przypisanie do zmiennej ( stor.s.pri fffffffc )
Tak jak poprzednio „podnosimy” stos i ustawiamy rejestr PRI na 0 ponieważ funkcja nie zwraca żadnej wartości. ( czyli w sumie zwraca 😛 )

Druga kiedy zwracamy tablice ( string to też tablica ):
Jest to trochę bardziej skomplikowane niż w pierwszym przypadku ale schemat jest prosty
Wszystko to dzieje się przed wywołaniem funkcji czyli callem

  • Na stos zostaje wrzucony adres zmiennej do której będzie zapisywane to co zwróci funkcji już PO powrocie do funkcji wywołującej
  • Dynamicznie na stercie zostaje zarezerwowana odpowiednia ilość pamięci
  • Adres do tej pamięci zostaje wrzucony na stos przed parametrami
  • Funkcja zwracając tablice zapisuje do tej zarezerwowanej pamięci
  • Kiedy sterowanie wróci do funkcji która wywołała call ze stosu pobierane są dwa parametry adres zmiennej do której ma być zapisana zwracana tablica oraz adres dynamicznie zarezerwowanej pamięci
  • Wartości są kopiowane
  • Pamięć na stercie jest zwalniana

Przykład
[pawn]public test3(){
new var[ 2 ] = { 0 , 1 };
return var;
}

public test5(){
new varReturn[ 2 ];
varReturn = test3();
}[/pawn]

i kod assemblerowy
[pawn]proc ; test3
;$lcl var fffffff8
stack fffffff8
zero.pri
addr.alt fffffff8
movs 8
addr.pri fffffff8
;$exp
load.s.alt c
movs 8
stack 8
retn

proc ; test5
;$lcl varReturn fffffff8
stack fffffff8
zero.pri
addr.alt fffffff8
fill 8
addr.pri fffffff8
push.pri
heap 8
push.alt
push.c 0
call test3
pop.pri
pop.alt
movs 8
heap fffffff8
;$exp
stack 8
zero.pri
retn[/pawn]

W proc test3 widzimy na początku zarezerwowanie pamięci wielkości 2 komórek ( 8 bajtów )
Skopiowanie do pri 0 które w tym przypadku oznacza adres danych przechowywanych w sekcji DATA
[pawn]{ 0 , 1 }[/pawn]
Tak wszystkie tego typu dane wpadają do sekcji data i są tam trzymane więc należy z tym uważać
Wcześniej dodałem wpis który porusza lekko ten temat ( http://darkgl.pl/index.php/2012/12/28/zarzadzanie-ciagami-znakow-w-pamieci-pluginu/ )
Zapisanie adresu do przed chwilą zarezerwowanej pamięci do rejestru ALT
Oraz skopiowanie 8 bajtów z pamięci pod adresem z PRI do pamięci pod adresem przechowywanym w ALT

Dalszy kod stanowi właśnie return tej funkcji
[pawn]addr.pri fffffff8
;$exp
load.s.alt c
movs 8[/pawn]

Mamy tu ponownie pobranie adresu zwracanej zmiennej do PRI oraz pobranie adresu pamięci zarezerwowanej w heap do ALT
i następuje kopiowanie 8 bajtów

Procedura test5 wygląda troche skomplikowanie ale jest w sumie prosta i jest w niej kod schematu który opisałem wyżej
[pawn]stack fffffff8
zero.pri
addr.alt fffffff8
fill 8[/pawn] Rezerwowanie pamięci pod zmienną i wypełnienie jej zerami

  1. Na stos zostaje wrzucony adres zmiennej
    [pawn]addr.pri fffffff8
    push.pri[/pawn]
  2. Dynamicznie na stercie zostaje zarezerwowana odopowiednia ilość pamięci
    [pawn]heap 8[/pawn]
  3. Adres do tej pąmięci zostaje wrzucony na stos przed parametrami
    [pawn]push.alt[/pawn]
  4. Kiedy sterowanie wróci do funkcji która wywołała call ze stosu pobierane są dwa parametry
    [pawn]pop.pri
    pop.alt[/pawn]
  5. Wartości są kopiowane
    [pawn]movs 8[/pawn]
  6. Pamięć na stercie jest zwalniana
    [pawn]heap fffffff8[/pawn]

I tyle 🙂

Teraz dlaczego nie ma przeciwskazań do zwracania hard coded strings
Co jest nam potrzebne do zwracania ?
– Wielkość danych
– Ich adres

i te wszystkie dane są dostępne dla hard coded strings co pokazuje ta funkcja
[pawn]public test4(){
new var[ 4 ] = „asd”;
return var;
}
[/pawn]
btw. można tego użyć takiego podejście jako obejście tego problemu ze zwracaniem 😉

Kod asemblerowy
[pawn]proc ; test4
;$lcl var fffffff0
stack fffffff0
const.pri 8
addr.alt fffffff0
movs 10
addr.pri fffffff0
;$exp
load.s.alt c
movs 10
stack 10
retn[/pawn]
Widzimy tutaj
Zarezerwowanie pamięci
[pawn]stack fffffff0[/pawn]
Zapisanie adresu ciągu „asd” w sekcji DATA do PRI
[pawn]const.pri 8[/pawn]
Skopiowanie adresu zmiennej do ALT
[pawn]addr.alt fffffff0[/pawn]
I samo skopiowanie danych
[pawn]movs 10[/pawn]

Mamy wszystkie potrzebne dane czy ten sam ( a przynajmniej podobny ) kod nie może być wykorzystany do zapisania danych przy return w pamięci na stercie 🙂 ?

Rejestry :
Bardzo krótko
PRI to alias na rejestr EAX ( do poczytania na wikipedii ) a ALT to alias na EDX
Służą do przechowywania różnego rodzaju wartości roboczych. Rejestrów innych też jest troche i są to
FRM wskaźnik na „stack frame” czyli ramkę stosu „w której” przechowywane są wartości potrzebne do powrotu kiedy
funkcja zakończy swoje działanie.
CIP adres aktualnie wykonywanej instruckji ( adres w pamięci )
DAT offset do miejsca gdzie zaczyna się sekcja data
COD offset do miejsca gdzie zaczyna się sekcja kodu
STP przechowuje adres gdzie zaczyna się stos
STK aktualna pozycja gdzie kończy się stos odkładając na stos odkładamy „w dół” więc stos dązdy od STP do zera , jest to tak zrobione
aby lepiej wykorzystac dostępna pamięć dla stosu i sterty ( heap ).
HEA aktualna pozycja gdzie kończy się sterta. Allokując pamieć dynamicznie jest ona rezerwowana właśnie na stercie.
Nie ma czegoś takiego jak rejestr z flagami. ( http://en.wikipedia.org/wiki/FLAGS_register )

Testowy plugin którego używałem przy pisaniu tego wpisu
[pawn]public test(){
return 1;
}

public test2(){
new var = 1;
return var;
}

public test3(){
new var[ 2 ] = { 0 , 1 };
return var;
}

public test4(){
new var[ 4 ] = „asd”;
return var;
}

public test5(){
new varReturn[ 4 ];
varReturn = test3();
}

public test6(){
new var;

var = test2();
}[/pawn]

Jeden komentarz o “Zwracanie hard coded strings i trochę o samym return

Dodaj komentarz

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.