2006/10/25

X-ohjelmointi - hello world

X Window System on verkkoläpinäkyvä ikkunointijärjestelmä. Järjestelmässä kaikki ikkunat ovat hierarkisessa järjestyksessä. Ylimmäisenä on juuri-ikkuna (roow window), jolla on lapsi-ikkunoita, joilla voi olla omia lapsi-ikkunoita.

X-protokolla on asynkroninen, jolloin kaikkea ei välttämättä piirretä heti kun funktiota kutsutaan. XSync() odottaa kunnes xserver on toteuttanut kaikki operaatiot.

Yhteys palvelimelle avataan funktiolla Display *XOpenDisplay(char *display_name); Display_name sisältää palvelimen osoitteen ja avattavan ruudun (screen) numeron. Se voi kuitenkin olla NULL, jolloin käytetään DISPLAY-ympäristömuuttujaa.

Ikkunan luominen

Ikkuna voidaan luoda XCreateWindow- tai XCreateSimpleWindow-funktiolla.
Window XCreateWindow(Display *display, Window parent, int x, int y, unsigned int width, unsigned int height, unsigned int border_width, int depth, unsigned int class, Visual *visual, unsigned long valuemask, XSetWindowAttributes *attributes);

Window XCreateSimpleWindow(Display *display, Window parent, int x, int y, unsigned int width, unsigned int height, unsigned int border_width, unsigned long border, unsigned long background);
Esimerkissä käytetään XCreateSimpleWindow-funktiota. Funktiolle kerrotaan siis näyttö, isäntäikkuna, x- sekä y-koordinaatit, leveys, korkeus, reunan väri, sekä reunan ja taustan väri.

Isäntäikkunan saa kutsulla RootWindow(display, DefaultScreen(display)).

X-protokolla sisältää mahdollisuuden siihen, että asiakas voi pyytää palvelinta ilmoittamaan tietyistä tapahtumista, kuten näppäimenpainalluksista tai hiiren liikkeistä. Tämä tapahtuu funktiolla XSelectInput. Seuraava koodinpätkä pyytää tiedot window-ikkunalle tapahtuvista hiirenpainalluksista sekä tiedon siitä kun ikkuna täytyy piirtää uudelleen.
XSelectInput(display, window, ButtonPressMask | ExposureMask);
Tämän jälkeen mapataan ikkuna näytölle (näkyväksi). XMapWindow(display, window);

Tapahtumien käsittely

Palvelin lähettää asiakkaalle asiakkaan haluamat tapahtumat ja ne siirtyvät asiakkaan tapahtumajonoon.

Tapahtumia voi tapahtumajonosta tarkastella seuraavilla funktioilla.

int XNextEvent(Display *display, XEvent *event_return);

int XPeekEvent(Display *display, XEvent *event_return);

int XWindowEvent(Display *display, Window w, long event_mask, XEvent *event_return);
XNextEvent palauttaa ensimmäisen tapahtuman tapahtumajonosta ja poistaa sen jonosta. Jos jono on tyhjä, funktio odottaa tapahtumia.

XPeekEvent palauttaa tapahtuman poistamatta sitä jonosta. XWindowEventillä voi puolestaan etsiä tapahtumia ikkunakohtaisesti.

Kaikki tapahtumat sisältävät tiedon niiden näytöstä, lähdeikkunasta ja lähdeajasta (koska kutsut tapahtuvat asynkronisesti niin niiden toteuttamisessa voi olla viivettä). Lisäksi tapahtumat sisältävät tapahtumakohtaisia tietoja, kuten hiiren liikkuessa hiiren koordinaatit.

Grafiikan piirtäminen

Jotta ikkunaan voisi piirtää, täytyy luoda piirtokonteksti (graphics context). Piirtokontekstille voit tämän jälkeen piirtää erilaisilla funktioilla. Tässä muutama ja lisää löytyy manuaalista:
  • XDrawLine
  • XDrawPoint
  • XDrawString
  • XFillPolygon
Seuraava koodinpätkä tulostaa ruudulle "Hello world!"
GC gc = XCreateGC(display, window, 0, 0);

const char *hello = "Hello world!";
XDrawString(display, window, gc, 30, 50, hello, std::strlen(hello));

XFreeGC(display, gc);

Esimerkkiohjelma

#include <X11/Xlib.h>
#include <cstring>
#include <iostream>

int main(int argc, char **argv)
{
Display *display = XOpenDisplay(NULL);;
if(display == NULL) {
std::cerr << "Can't connect to the xserver.\n";
return 1;
}

int screen_num = DefaultScreen(display);

Window window = XCreateSimpleWindow(display,
RootWindow(display, screen_num),
200, 200, 200, 200, 10,
BlackPixel(display, screen_num),
WhitePixel(display, screen_num));

XSelectInput(display, window,
ButtonPressMask | ExposureMask);

XMapWindow(display, window);

GC gc = XCreateGC(display, window, 0, 0);

bool running = true;
while(running) {
XEvent event;
XNextEvent(display, &event);

switch(event.type) {
case Expose:
{
const char *hello = "Hello world!";
XDrawString(display, window, gc, 30, 50,
hello, std::strlen(hello));
break;
}
case ButtonPress:
running = false;
default:
break;
}
}

XUnmapWindow(display, window);
XFreeGC(display, gc);
XDestroyWindow(display, window);
XCloseDisplay(display);
return 0;
}
  • g++ -g xhello.cc -o xhello -lX11
  • ./xhello
Ikkuna näyttää tältä Enlightenmentia käytettäessä.

X programming hello world

Ilman ikkunamanageria ikkuna voisi näyttää tältä. Ikkunalla ei ole otsikkopalkkia eikä sitä voi liikuttaa. (Voit tietenkin koodata ohjelmaan otsikkopalkin ja napata hiiritapahtumat ja kutsua XMoveWindow-funktiota)

Ohjelman voi sammuttaa napsauttamalla sitä hiirellä.

X programming without window manager

2006/10/06

Stack trace linuxilla (osa 2)

Ensimmäisessä osassa käsiteltiin pinolistauksen tulostamista backtrace- ja backtrace_symbols-funktioiden avulla. Tarkempaa tietoa, kuten tiedostonnimi ja rivinumero, saa ohjelmalla addr2line(1).
addr2line translates addresses into file names and line numbers. Given an address in an executable or an offset in a section of a relocatable object, it uses the debugging information to figure out which file name and line number are associated with it.
Addr2line:n syntaksi on seuraava: -f-vipu tulostaa funktion, -C-dekoodaa sen (demangle) ja -e:llä määritellään binääri. Ohjelma haluaa vielä osoitteen johonkin kohtaan ohjelmassa. Esimerkkikäyttö, joka lukee a.out tiedoston: "addr2line 0x8048796 -f -C".

Osoite, jonka addr2line haluaa on sama, joka löytyy backtrace_symbolsin palauttamasta taulukosta. Osoite on hakasulkujen välissä (./a.out(_Z5tracev+0x20) [0x8048956]).

Testihjelma:
#include <execinfo.h>
#include <iostream>

void trace()
{
const int ARRAY_SIZE = 5;
void *array[ARRAY_SIZE];

int size = backtrace (array, ARRAY_SIZE);
char **strings = backtrace_symbols(array, size);
if(strings == NULL)
return;

for( int i=0; i<size; ++i)
std::cout << strings[i] << std::endl;

free(strings);
}

void funktio()
{
trace();
}

int main()
{
funktio();
return 0;
}
Jos ohjelman kääntää normaalisti (g++ trace.cc) saadaan addr2linen tulostukseksi seuraavaa. Kuten huomaat niin funktio nimi näkyy ilman -rdynamic-vipua.
trace()
??:0
Jos rivinumerot ja tiedosto halutaan näkyviin, pitää ohjelmaan lisätä debug-tietoa -g-vivulla.
trace()
/home/bjqcqafprnj/stacktrace/trace.cc:10
Ohjelman sisällä addr2linen voi suorittaa esimerkiksi funktiolla popen(3).

2006/10/05

Funktiokutsun korvaaminen kirjastosta

Kun ohjelma käynnistetään Linuxilla niin ld-linux.so(8) lataa ohjelman tarvitsemat kirjastot ja suorittaa ohjelman. Kirjastoja etsitään seuraavista paikoista.
  • LD_LIBRARY_PATH
  • /etc/ld.so.cache
  • /usr/lib
  • /lib
Oman kirjaston saa kuitenkin latautumaan lisäämällä sen LD_PRELOAD-ympäristömuuttujaan. Muuttuja sisältää välilyönnein erotellun listan kirjastoista, jotka ladataan ennen muita, mahdollistaen kirjastojen tai yksittäisten funktioiden korvaamisen.

Kirjastossa ei tarvitse muuta kuin määritellä funktio oikealla nimellä. Esimerkkikirjasto korvaa getpid-kutsun funktiolla, joka palauttaa aina 123.
#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void)
{
return 123;
}
Kirjaston voi kääntää komennolla gcc -shared -Wl,-soname,getpid_replacement.so.1 -o getpid_replacement.so getpid_replacement.c -Wall

Testiohjelma, joka kutsuu getpidiä.
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main(void)
{
printf("getpid() = %d\n", getpid());
return 0;
}
Nyt kun ohjelman ajaa se voi tulostaa "getpid() = 7570". Seuraavaksi pitää asettaa ympäristömuuttujat kuntoon ja ajaa ohjelma korvatulla getpidillä.
  • $ export LD_PRELOAD=getpid_replacement.so
  • $ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
  • $ ./a.out
  • getpid() = 123
Tätä tekniikkaa voidaan myös käyttää väärin esimerkiksi korvaamalla read- tai scanf-funktiot (käyttäjä syöttää salasanan) ja lisäämällä export-rivit tiedostoon ~/.bashrc, jolloin korvaava kirjasto ladataan kaikille ohjelmille.

Tässä vielä versio, joka tulostaa prosessin oikean pidin ja palauttaa väärän.
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>

inline int get_real_pid(void)
{
asm("mov $20, %%eax\n"
"int $0x80\n" : :);
}

pid_t getpid(void)
{
printf("Original pid:%d\n", get_real_pid());
return 123;
}

2006/10/04

Heikkous Linuxin kernelin binääritiedostojen käsittelyssä

Slashdot uutisoi eilen heikkoudesta kernelissä. Heikkous liittyy siihen miten kerneli käsittelee binääritiedostojen formaatteja.

Kun execv-funktiota kutsutaan systeemikutsua numero 11 (sys_execv), joka kutsuu funktiota do_execve (tiedosto fs/exec.c).

do_execve-funktiossa etsitään tiedoston neljän ensimmäisen tavun perusteella tiedostolle käsittelijää linkitetystä listasta.
int search_binary_handler(struct linux_binprm *bprm,struct pt_regs *regs)
{
int try,retval;
struct linux_binfmt *fmt;
[...]
for (fmt = formats ; fmt ; fmt = fmt->next) {
int (*fn)(struct linux_binprm *, struct pt_regs *) = fmt->load_binary;
if (!fn)
continue;
[...]
Haavoittuvuus johtuu siitä, että kun kerneliin lisätään oma binääritiedostojen käsittelijä niin se lisätään linkitetyn listan alkuun, jolloin sitä kutsutaan aina kun tiedostoa yritetään suorittaa.

$ ls
-> sys_execve("/bin/ls", ["ls"], [...])
-> do_execve
-> search_binary_handler([...])
rootkit = -ENOEXEC // suorittaa rootkit-koodin ja palauttaa -ENOEXEC
script_handler // tiedosto ei ala merkeillä #!
elf_handler // tiedosto on elf-binääri, suorittaa koodin
Koska käsittelijä toimii täysin kernelin sisällä sitä ei voi havaita stracella eikä ltracella. Heikkouden parantamiseksi ehdotetaankin tiedostonkäsittelijöiden lisäämistä linkitetyn listan loppuun.

2006/10/03

Stack trace linuxilla (osa 1)

Pinojäljityksen saa aikaiseksi funktioilla backtrace ja backtrace_symbols, jotka löytyvät GNU:n C-kirjastosta.

Sitten itse asiaan. Esimerkkiohjelma joka demonstroi funktioita.

#include <execinfo.h>
#include <iostream>

void trace()
{
const int ARRAY_SIZE = 5;
void *array[ARRAY_SIZE];

int size = backtrace (array, ARRAY_SIZE);
char **strings = backtrace_symbols(array, size);
if(strings == NULL)
return;

for( int i=0; i<size; ++i)
std::cout << strings[i] << std::endl;

free(strings);
}

void funktio()
{
trace();
}

int main()
{
funktio();
return 0;
}
Käännettynä komennolla g++ trace.cc -Wall:

./a.out(__gxx_personality_v0+0x172) [0x8048796]
./a.out(__gxx_personality_v0+0x1e7) [0x804880b]
./a.out(__gxx_personality_v0+0x200) [0x8048824]
/lib/tls/libc.so.6(__libc_start_main+0xc8) [0xb7d17ea8]
./a.out(__gxx_personality_v0+0x5d) [0x8048681]
Tuloste ei ole kovinkaan hyödyllinen sellaisenaan. Sama koodi käännettynä -rdynamic-vivulla:

./a.out(_Z5tracev+0x20) [0x8048956]
./a.out(_Z7funktiov+0xb) [0x80489cb]
./a.out(main+0x16) [0x80489e4]
/lib/tls/libc.so.6(__libc_start_main+0xc8) [0xb7d6bea8]
./a.out(__gxx_personality_v0+0x51) [0x8048841]
Manuaalisivulta ld(1):
--export-dynamic
When creating a dynamically linked executable, add all symbols to the dynamic symbol table. The dynamic symbol table is the set of symbols which are visible from dynamic objects at run time.
Tulosteesta kuitenkin huomaa, että funktioiden nimet näyttävät oudoilta, koska nimet mankeloidaan muun muassa tilan säästämiseksi. Vertaa:
myclass& myclass::operator=(const myclass& from)
??4myclass@@QAEAAV0@ABV0@@Z

Muutama dokumentti nimien mankeloinnista.

GNU:n libstdc++ sisältää funktion näiden tietojen demankeloimiseen.
char* abi::__cxa_demangle(const char *      mangled_name,
char * output_buffer,
size_t * length,
int * status
)

2006/10/02

dectohex

decToHex-funktio C:llä

const char *dec_to_hex(unsigned long dec)
{
static char buf[9];
for(int i=7;i>=0;i--)
buf[(7-i)]=((dec>>i*4)&0xF)["0123456789ABCDEF"];
buf[8] = 0;
return buf;
}

Kommentteja?

2006/10/01

std::accumulate

accumulate "laskee yhteen" elementit, jotka sille annetaan.

Funktiosta on kaksi versiota, joista jälkimmäinen käyttää funktiota operator+ __binary_op:na. Laskeminen aloitetaan alkuarvosta __init.
_Tp std::accumulate(_InputIterator __first,
_InputIterator __last,
_Tp __init,
_BinaryOperation __binary_op
)

_Tp std::accumulate(_InputIterator __first,
_InputIterator __last,
_Tp __init
)
Algoritmin käyttäminen on helppoa, kun säiliössä on tavallisia numeroita:
#include <iostream>
#include <numeric>
#include <list>

int main()
{
std::list<float> lista;
lista.push_back(3);
lista.push_back(4);
lista.push_back(4);

std::cout << std::accumulate(lista.begin(), lista.end(), 1) << std::endl;
}

Ohjelma antaa kääntyessään varoituksen:
In function ‘_Tp std::accumulate(_InputIterator, _InputIterator, _Tp) [with _InputIterator = std::_List_iterator<float>, _Tp = int]
warning: converting to ‘int’ from ‘float’


Se korjaantuukin vaihtamalla 1:n tilalle static_cast<float>(1).

Ohjelma tulostaa 12 (3+4+4 = 11 ja alkuarvona 1).

Accumulaten käyttö oman luokan kanssa

Jos yhteen halutaan laskea muutakin kuin perustietotyyppejä täytyy määritellä operator+. Jos laskettavana on luokkia X, täytyy operator+ määritellä seuraavasti int operator+(int, const X &).

Tässä luokka, jonka sisältöä lasketaan:

class X {
public:
X(int i):m_i(i) { }
int get_i() const { return m_i; }
private:
int m_i;
};

operator+

Esimerkki:

int operator+(int n, const X &x) { return n + x.get_i(); }

std::vector<X> y;
y.push_back(1);
y.push_back(2);
int n = std::accumulate(y.begin(), y.end(), 0); // palauttaa 3

Tämä ei kuitenkaan toimi, jos säiliössä on osoittimia. Katso kaksi seuraavaa.

Muu funktio

Määritetään funktio, joka ottaa kaksi parametriä. Tässä on vikana, että se sotkee nimiavaruutta ja jos se määritellään väärässä paikassa niin sitä ei välttämättä osaa yhdistää oikeaan tarkoitukseen.

int plus(int i, X *x) {
return i + x->get_i();
}
Siirretäänkin se X-luokan funktioksi. Tällöin päästääm myös käsiksi m_i-muuttujaan ilman getteriä.

class X {
public:
static int plus(int i, X *x) {
return i + x->m_i;
}
};
std::vector<X*> x;
int n = std::accumulate(x.begin(), x.end(), 0, X::plus);

TR1

Laskeminen voidaan myös hoitaa C++:n seuraavaan standardiin lisättävien komponenttien avulla, jotka löytyvät GCC 4:stä. (TR1)

Boost-kirjastoon tutustuneille koodi näyttää varmasti heti selvältä. Tämän ratkaisun huonoja puolia ovat verrattain pitkä kääntymisaika johtuen massiivisesta mallien (template) sekä ylikuormitusten käytön takia.

int n = std::accumulate(x.begin(), x.end(), 3,
std::tr1::bind(std::plus<int>(),
std::tr1::placeholders::_1,
std::tr1::bind(&X::get_i,
std::tr1::placeholders::_2)));

Esimerkkiohjelman voit ladata täältä: http://pastebin.ca/raw/186941

2006/09/30

GINA ja Credential Providers

GINA, graphical identification and authentication, on Windowseissa käytetty DLL-kirjasto joka tarjoaa käyttäjälle kirjautumistoiminnot ja se ladataan Winlogon-prosessissa. Tarjotakseen omat kirjautumistoiminnot, käyttäjän täytyy korvata MSGina.dll omallaan. DLL-tiedoston täytyy implementoida tietyt funktiot.

Kirjaston korvaamisella voidaan esimerkiksi sallia kirjautuminen sisään sormenjälkitunnistimella. DLL-tiedoston vastuulla on myös käyttäjän kanssa keskusteleminen kun käyttäjä painaa Ctrl+Alt+Del ja sen täytyy implementoida muutkin GUI-toiminnot.

VistassaGINA on korvattu uudella tekniikalla, nimeltään Credential Provider. Tässä tekniikassa LogonUI ja Winlogon keskustelevat keskenään, jolloin asioista saadaan skaalautuvampia. Vistassa tämä uusi tekniikka helpottaa ja yksinkertaistaa asioita, koska enää ei tarvitse ohjelmoida koko käyttöliittymää uudestaan tietyn toiminnon aikaansaamiseksi.

Uusi malli mahdollistaa myös useampien Credential Providerien asentamisen käyttöjärjestelmään. Näistä käyttäjä voi sitten valita haluamansa. Vistassa tuleekin mukana kaksi eri Credential Provideria: se tavallinen, eli käyttäjänimi ja salasana sekä sirukortilla kirjautumisen mahdollistava Credential Provider.

2006/09/28

bjqcqafprnj

bjqcqafprnj