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.
gcc main.cpp -S -masm=intel
Tutorial do asemblera x86:

4 sierpnia 2014

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.
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 pt
A 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.cpp
Hmm, obyło się bez protestów. Jeszcze jedna próba tym razem z -Wextra
gcc -Wall -Wextra -pedantic -ansi main.cpp
Tutaj 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).
g++ -S main.cpp 
as main.s -o main.o
g++ main.o -o main
Nasz 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.
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 -S
I 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)