gcc main.cpp -S -masm=intelTutorial do asemblera x86:
ekstaza, geniusz, przebłysk, olśnienie, półprawdy, półśrodki, przemilczenia, zaćmienia, głupstwa, kłamstewka, oszustwa, hultajstwo, wyrachowanie, nieprawda, nieobiektywność, niepodważalna prawda, nierówność, nieomylność, słuszność, perfekcja, krnąbrność ... niegodziwość
Pokazywanie postów oznaczonych etykietą Asm. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą Asm. Pokaż wszystkie posty
5 sierpnia 2014
[gcc + tutorial] x86 Assembly
Zobaczyłem fajny przełącznik (-masm) do gcc, który wyrzuca kod asemblera bardziej przypominający MASM niż NASM.
4 sierpnia 2014
Back to crackme
Wróciłem po latach do zabawy z crackme, z którymi wcale tak dużo się nie bawiłem. Zaczynam od nauki narzędzi i oglądanie podcastów Gynvaela Coldwinda - ReverseCraft:
Link z listą instrukcji procesora:
Link z listą instrukcji procesora:
5 lutego 2013
Deklaracja zmiennych i trochę o const/constexpr
Najpierw małe przypomnienie, jak wygląda deklaracja zmiennych w C++. Rzadko stosuję i spotykam deklaracje zmiennych jakiegoś typu po przecinku, stąd czytając książkę, sam byłem zaskoczony, co tak naprawdę znaczył ten kod.
A teraz o nowościach. const expression jest wyrażeniem, którego wartość nie może zostać zmieniona, i dla którego wartość powinna być (ale nie musi) obliczona podczas kompilacji. W C++11 pojawia się nowy modyfikator constexpr, który zbada, czy tak rzeczywiście się stanie. Funkcje z constexpr muszą być na tyle proste by kompilator poradził sobie z nimi podczas kompilacji, ale w kolejnych wersjach standardu konstrukcje będą stawać się coraz bardziej skomplikowane (pętle, algorytmy STL, kontenery itp.). W zamyśle constexpr ma stać się alternatywą dla metaprogramowania w szablonach.
int i, *ip = nullptr; // i jest typu int, ip jest wskaźnikiem na int.
int* p1, p2; // p1 jest wskaźnikiem na int, p2 jest typu int.
int* a, &b = *ip; // a jest wskaźnikiem na int, b jest referencją na wartość
// wskazywaną przez ip.
A teraz trochę o const. Zawsze jest to dla mnie mylące, przynajmniej po dłuższym okresie czasu, gdy wiedza powolutku wyparowuje.const int c = 8; // c jest stałą typu int, jego wartość nie może być zmieniona.
const int &r = c; // ok, referencja na stały obiekt typu int - mówimy co będziemy mogli
// robić przy pomocy referencji - odwołując się do r nie damy rady
// zmodyfikować c (chociaż sam w sobie nie jest const).
int &r2 = c; // error, referencja na niestały obiekt.
Nie ma czegoś takiego jak stałe referencje (z technicznego punktu widzenia), są tylko referencje na stałe. Ponieważ nie można zmusić referencji by wskazywała na inny obiekt w jakimś sensie wszystkie referencje są stałe.double dval = 3.14; const double *pt = &dval; // ok (tak jak wyżej), ale nie możemy zmieniać dval, przy pomocy ptA teraz co powstanie, gdy const znajdzie się w różnych miejscach? Pewne oznaczenia - cytując za książką:
Używamy terminu top-level const do oznaczenia, że sam wskaźnik jest stały. Kiedy wskaźnik może wskazywać na stały obiekt, odnosimy się do tego jako low-level const
int i = 0;
const int c1 = 88; // c1 jest stałym obiektem typu int
int const c2 = 99; // (TO SAMO) c2 jest stałym obiektem typu int
int *const p1 = &i; // p1 jest stałym wskaźnikiem na obiekt typu int (top-level const).
const int ci = 42; // ci jest stałym obiektem typu int (top-level const).
const int *p2 = &ci; // p2 jest wskaźnikiem na stały obiekt typu int (low-level).
int const *p3 = &ci; // (TO SAMO) p3 jest wskaźnikiem na stały obiekt typu int.
const int *const p4 = p2; // p3 jest stałym wskaźnikiem na stały obiekt typu int
// (prawy const jest top-level; lewy nie)
const int &r = i; // referencja na stały obiekt typu int.
// const w typach referencyjnych zawsze jest low-level.
W odróżnieniu od referencji, wskaźniki są obiektami, więc mogą być const, co znaczy, że po ustawieniu w momencie inicjalizacji nie mogą na nic innego wskazywać. A teraz o nowościach. const expression jest wyrażeniem, którego wartość nie może zostać zmieniona, i dla którego wartość powinna być (ale nie musi) obliczona podczas kompilacji. W C++11 pojawia się nowy modyfikator constexpr, który zbada, czy tak rzeczywiście się stanie. Funkcje z constexpr muszą być na tyle proste by kompilator poradził sobie z nimi podczas kompilacji, ale w kolejnych wersjach standardu konstrukcje będą stawać się coraz bardziej skomplikowane (pętle, algorytmy STL, kontenery itp.). W zamyśle constexpr ma stać się alternatywą dla metaprogramowania w szablonach.
#include <iostream>
#include <stdio.h>
using namespace std;
constexpr int get_size()
{
return 6;
}
int main()
{
constexpr int sz = get_size() + 80; // ok, pod warunkiem, że funkcja też jest constexpr.
printf("%d", sz);
return 0;
}
I podgląd tego co wyprodukował kompilator (g++ -std=c++17 -S -masm=intel main.cpp). Jak widać w pliku binarnym pojawia się już obliczona wartość (80+6=86).main:
.LFB1824:
.cfi_startproc
endbr64
push rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
mov rbp, rsp
.cfi_def_cfa_register 6
sub rsp, 16
mov DWORD PTR -4[rbp], 86
mov esi, 86
lea rdi, .LC0[rip]
mov eax, 0
call printf@PLT
mov eax, 0
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
7 listopada 2012
Struct - inicjalizacja zerami i -Wextra (część II)
A co gdyby, do inicjalizacji użyć pustych klamerek? Na stronie http://www.ex-parrot.com/~chris/random/initialise.html można przeczytać:
It would be neater to be able to say struct foo bar = {}, and some compilers (e.g. GCC) permit this, but the ANSI grammar requires an initialiser list to be non-empty.Czyli generalnie nie można, bo niezgodne ze standardem. Trzeba więc przekonać się na własnej skórze.
struct Stru2 {
int c;
int d;
};
struct Stru1 {
int a;
Stru2 b;
};
int main()
{
struct Stru1 aaa = {};
return aaa.b.c;
}
Mimo, że GCC nam na to pozwala, to chciałem wymusić zgodność ze standardem ANSI, (chociaż bardziej mnie interesuje co się stanie w C++, a nie w C).gcc -Wall -pedantic -ansi main.cppHmm, obyło się bez protestów. Jeszcze jedna próba tym razem z -Wextra
gcc -Wall -Wextra -pedantic -ansi main.cppTutaj się już coś dzieje. Ciekawe sprawa, bo nawet gdy skorzystam z zapisu "Stru1 aaa = {0}", to dalej są protesty o nie zainicjowane zmienne.
main.cpp:36:25: warning: missing initializer for member ‘Stru1::a’ [-Wmissing-field-initializers] main.cpp:36:25: warning: missing initializer for member ‘Stru1::b’ [-Wmissing-field-initializers]Zasięgnąłem opinii na stackoverflow.com i z odpowiedzi wynika, że -Wextra, martwi się bardziej niż pozawala na to standard i choć wszystko zostanie zainicjalizowane zerami, to teraz kompilator chciałby, żebyśmy sami zainicjalizowali każde pole z osobna. Jeszcze rzut oka, na to co wygenerował kompilator. Wszystko na zero ;)
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $16, %esp
movl $0, -12(%ebp)
movl $0, -8(%ebp)
movl $0, -4(%ebp)
movl -8(%ebp), %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
1 listopada 2012
Kompilacja kodu assemblera i łączenie z g++
Jak zmusić kompilator do wygenerowania kodu assemblera, by go zmodyfikować i ponownie użycia do kompilacji?
Magiczny ciąg komend:
Po modyfikacji kodu assemblera, generujemy object-file, z którego ponownie skorzysta g++ (linijka 2).
Magiczny ciąg komend:
Po modyfikacji kodu assemblera, generujemy object-file, z którego ponownie skorzysta g++ (linijka 2).
g++ -S main.cpp as main.s -o main.o g++ main.o -o mainNasz kod, który chcemy zobaczyć w assemblerze:
#include <stdio.h>
int main()
{
printf("Hello world\n");
return 0;
}
Nasza modyfikacja w kodzie assemblerze (main.s):.file "main.cpp"
.section .rodata
.LC0:
.string "Hello Kitty ;)"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $16, %esp
movl $.LC0, (%esp)
call puts
movl $0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
.section .note.GNU-stack,"",@progbits
Wynik:Hello Kitty ;)
26 sierpnia 2012
C struct - inicjalizacja zerami
Dowiedziałem się (http://www.ex-parrot.com/~chris/random/initialise.html), że można zainicjować całą strukturę zerami, korzystają z zapisu jak poniżej. Obejmuje to również zagnieżdżone pola.
W dodatku (przez brak optymalizacji przy kompilacji), zapis do aaa.i będzie dwukrotny:
- aaa.i wypełnione "0" (linia 17)
- aaa.i wypełnione "5" (linia 19)
struct SAaa {
int i;
int w;
};
int main() {
struct SAaa aaa = { 0 };
return 0;
}
Gdzieś doczytałem, że gdy w miejsce zera wstawię coś innego, to efekt będzie odmienny.int main() {
struct SAaa aaa = { 5 };
return 0;
}
Kompilacja, bez flag optymalizacji:$ g++ main.cpp -m32 -masm=intel -SI to co wyprodukuje kompilator:
.file "main.cpp"
.intel_syntax noprefix
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
push ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
mov ebp, esp
.cfi_def_cfa_register 5
sub esp, 16
call __x86.get_pc_thunk.ax
add eax, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_
mov DWORD PTR -8[ebp], 0
mov DWORD PTR -4[ebp], 0
mov DWORD PTR -8[ebp], 5
mov eax, 0
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.section .text.__x86.get_pc_thunk.ax,"axG",@progbits,__x86.get_pc_thunk.ax,comdat
.globl __x86.get_pc_thunk.ax
.hidden __x86.get_pc_thunk.ax
.type __x86.get_pc_thunk.ax, @function
__x86.get_pc_thunk.ax:
.LFB1:
.cfi_startproc
mov eax, DWORD PTR [esp]
ret
.cfi_endproc
.LFE1:
.ident "GCC: (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005"
.section .note.GNU-stack,"",@progbits
Cóż, tego się nie spodziewałem - pewnie dlatego, że nigdy nie przebiłem się przez standard. Pierwsze pole dostaje piątkę, reszta nadal inicjowana jest zerami. W dodatku (przez brak optymalizacji przy kompilacji), zapis do aaa.i będzie dwukrotny:
- aaa.i wypełnione "0" (linia 17)
- aaa.i wypełnione "5" (linia 19)
Subskrybuj:
Posty (Atom)