17 października 2015

[C++11] std::future - programowanie oparte na zadaniach

W programowaniu współbieżnym preferowanym podejściem powinno być programowanie oparte na zadnich, a nie na wątkach [1]. Kilka przydatnych linków:
std::future jest obiektem, przechowującym wyniki (także wyjątki) asynchronie działających funkcji - czyta wynik ze współdzielonego stanu. Wynik można pozyskać za pomocą metody get(). W takim scenariuszu wątek główny zostanie zablokowany do chwili aż wynik będzie dostępny, lub zwróci wynik natychmiast, jeżeli asynchroniczna operacja zakończyła się wcześniej.

Do asynchronicznego wołania funkcji służy std::async. Jest to rozwiązanie najbardziej wysokopoziomowe, gdyż sam std::async dba o to by ustawić std::future w stan ready. Wywołanie oprócz ewentualnych argumentów jakie mają być przekazane do funkcji wymaga również podania policy:
  • std::launch::async - nowy wątek zostanie uruchomiony do wykonania zadania
  • std::launch::deferred - wykonanie zadania zostaje odroczone do momentu wywołania metod get() lub wait()
  • std::launch::async | std::launch::deferred - kombinacja flag, jednak zachowanie jest zależne od implementacji
Wszystkie przykłady kompilowałem w ten sam sposób, z dodatkowo włączonym sanitizerem, który pomógł wykryć kilka błędów w kodzie.
$ clang++ -std=c++14 -fsanitize=thread -lpthread -g main.cpp
Przykład obliczania ciągu Fibonacciego z zastosowaniem std::async.
#include <iostream>
#include <future>

using namespace std;

int fibonacci(int n)
{
    std::cout << "Current n: " << n << std::endl;
    if (n == 0)
        return 0;
    else if (n == 1)
        return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main()
{
    std::future<int> result = std::async(std::launch::async | std::launch::deferred,
                                         fibonacci, 3);
    std::cout << "Result: " << result.get() << std::endl;

    return 0;
}
Wynik:
Current n: 3
Current n: 2
Current n: 1
Current n: 0
Current n: 1
Result: 2
std::packaged_task także pozawala na tworzenie obiektów std::future. Można go porównać do std::function, a więc jako wrapper do tworzenia obiektów callable. W przeciwieństwie do std::async, nie uruchamia on jednak przekazanej funkcji automatycznie. Najważniejszą właściwością std::packaged_task jest możliwość pozyskania z niego obiektu std::future, i przekazania go do innego wątku, gdzie zostanie wykonany.

Przykład:
#include <iostream>
#include <thread>
#include <future>

using namespace std;

int fibonacci(int n)
{
    std::cout << "Current n: " << n << std::endl;
    if (n == 0)
        return 0;
    else if (n == 1)
        return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main()
{
    std::packaged_task<int(int)> task{fibonacci};
    std::future<int> result = task.get_future();

    std::thread t{std::move(task), 3};
    t.join();

    std::cout << "Result: " << result.get() << std::endl;

    return 0;
}
Wynik:
Current n: 3
Current n: 2
Current n: 1
Current n: 0
Current n: 1
Result: 2
std::promise można rozważyć jako początek kanału komunikacyjnego, na którego końcu znajduje się std::future. Do jego zadań, należy zapisanie wyniku do współdzielonego stanu. Czasami dane, które nas interesują są już dostępne (obliczone) i nie musimy czekać na zakończenie jakiegoś wątku. Za pomocą std::promise możemy w wątku głównym wytworzyć obiekt std::future (czyli końcówka kanału - dzięki metodzie get_future()), a następnie przekazać go (std::promise) do innego wątku. W wątku pobocznym, gdy interesujące nas dane zostaną obliczone wołamy metodę set_value() (początek kanału). W ten sposób obiekt std::future zostanie ustawiony w stan ready, i dane będą mogły być odczytane w wątku głównym. W porównywaniu do std::async, jest to rozwiązaniem bardziej nisko poziomowe, lecz czasami zachodzi potrzeba aby z niego skorzystać.

std::promise ma jeszcze jedno zastosowanie, można go wykorzystać jako mechanizm do sygnalizacji pomiędzy wątkami (std::promise<void> - typ obiektu jako void). Coś na kształt std::condition_variable, z tym że taka sygnalizacja może zadziać się tylko raz. Aby odblokować pracę na współdzielonych danych należy wywołać set_value(). Aby wstrzymać pracę wątku do momentu, aż dane będą dostępne, należy wywołać metodę wait().

Przykład:
#include <iostream>
#include <thread>
#include <future>

using namespace std;

struct Fibonacci {
    std::promise<int> p;
    bool isSatisfied;
    const int index;
    int partialIndex;

    Fibonacci(std::promise<int> p_, int index_)
        : p(std::move(p_))
        , isSatisfied(false)
        , index(index_)
        , partialIndex(0)
    {
    }

    int fibonacci(int n)
    {
        std::cout << "Current n: " << n << std::endl;

        int result = 0;
        if (n == 0)
            result = 0;
        else if (n == 1)
            result = 1;
        else
            result = fibonacci(n - 1) + fibonacci(n - 2);

        if (isSatisfied == false and partialIndex == n) {
            isSatisfied = true;
            std::cout << "Passing partial result" << std::endl;

            p.set_value(result);
        }
        return result;
    }

    void operator()(int partialVal_)
    {
        partialIndex = partialVal_;
        fibonacci(index);
    }
};

int main()
{
    std::promise<int> p;
    std::future<int> result = p.get_future();

    std::thread t{ Fibonacci{ std::move(p), 5 }, 3 };

    std::cout << "Result: " << result.get() << std::endl;
    t.join();

    return 0;
}
Przykładowy wynik:
Current n: 5
Current n: 4
Current n: 3
Current n: 2
Current n: 1
Current n: 0
Current n: 1
Passing partial result
Current n: 2
Current n: 1
Current n: 0
Current n: 3
Current n: 2
Result: 2Current n: 
1
Current n: 0
Current n: 1
Bibliografia:
  • [1] Scotta Meyersa: Skuteczny nowoczesny C++. APN PROMISE SA, 2015. Rozdział 7, str. 289.
  • [2] Anthony Williams: C++ Concurency in Action. USA Manning publications Co., 2012. Rozdział 7, str. 67.

2 października 2015

[C++11] std::condition_variable i współdzielenie danych między wątkami

Praca na danych wymaga mechanizmu, dzięki któremu wątki będą mogły zablokować swoje działania do czasu, aż z danych nie będzie korzystał żaden inny wątek. Najprostszym i zarazem najmniej efektywnym rozwiązaniem może być pętla i próba założenia blokady na muteks. Biblioteka standardowa na szczęście oferuje znacznie lepsze rozwiązanie w postaci std::condition_variable.

std::condition_variable wymaga do działania muteksów i std::unique_lock (ze względu na efektywność). Pozwala to na większą elastyczność, gdyż muteks może zostać zablokowany i odblokowany w dowolnym momencie (std::lock_guard wykorzystuje RAII). Klasa posiada dwa rodzaje metod: wait_* (wait, waif_for, wait_until) oraz notify_* (notify_one, notify_all). Te pierwsze blokują działanie wątku do czasu, aż zostanie spełniony warunek który został przekazany do wait_*. Może to być lambda, punkt w czasie lub odcinek czasu. Natomiast notify_* zajmuje się wybudzaniem wątków.

Ciekawostka, o której dowiedziałem się z cppreference. Przed wołaniem notify_*, należy ręczenie zawołać unlock na obiekcie std::unique_lock (choć destruktor tego obiektu i tak by to wykonał). W ten sposób unikamy sytuacji, w której wybudzamy wątek, tylko po to by ponownie go uśpić, ponieważ warunek nie został jeszcze osiągnięty.
Przykład.
#include <condition_variable>
#include <thread>
#include <mutex>
#include <list>
#include <string>
#include <iostream>

using namespace std;

std::mutex m;
std::condition_variable cv;
std::list<string> train;

void dig_coal() {
    int resource = 4;
    while(resource >= 0) {
        std::unique_lock<std::mutex> l{m};
        cv.wait_for(l, std::chrono::milliseconds{400});

        if (resource > 0) train.push_back("coal");
        else train.push_back("empty");
        resource -= 1;
        std::cout << "Added trolley, train length: " << train.size() << std::endl;

        l.unlock();
        cv.notify_one();
    }

    std::cout << "No more coal to mining." << endl;
}

void burn_coal() {
    while(true) {
        std::unique_lock<std::mutex> l{m};
        cv.wait(l, []{ return not train.empty(); });

        const string trolley = train.front();
        train.pop_front();
        std::cout << "Coal burn, train length: " << train.size() << std::endl;

        l.unlock();
        cv.notify_one();

        if(trolley == "empty") break;
        std::this_thread::sleep_for(std::chrono::milliseconds{600});
    }

    std::cout << "All coal burn." << endl;
}

int main() {
    std::thread miner{dig_coal};
    std::thread power_station{burn_coal};

    cv.notify_one();

    miner.join();
    power_station.join();

    return 0;
}
Wynik:
Added trolley, train length: 1
Coal burn, train length: 0
Added trolley, train length: 1
Added trolley, train length: 2
Coal burn, train length: 1
Added trolley, train length: 2
Added trolley, train length: 3
No more coal to mining.
Coal burn, train length: 2
Coal burn, train length: 1
Coal burn, train length: 0
All coal burn.

1 października 2015

kompilacja clang-a i libc++

Z kolejnymi wydaniami, pojawiają się delikatne różnice w procesie instalacji, warto więc śledzić poniższe linki.
Ściągnięcie źródeł + instalacja:
git clone http://llvm.org/git/llvm.git

cd llvm/tools
git clone http://llvm.org/git/clang.git

cd llvm/projects
git clone http://llvm.org/git/compiler-rt.git

cd llvm/projects
git clone http://llvm.org/git/libcxx.git
git clone http://llvm.org/git/libcxxabi.git

cd llvm/projects
git clone http://llvm.org/git/test-suite.git

mkdir llvm_build
mkdir llvm_root
cd llvm_build

cmake -G "Unix Makefiles" DCMAKE_INSTALL_PREFIX=/home/beru/llvm_root/ ../llvm
make -j2
make install
Kompilacja prostego Hello World. Dzięki temu zlepkowi argumentów jestem w stanie używać przekompilowanej przez siebie wersji biblioteki standardowej z przekompilowaną wersją clang-a jak i tą pochodzącą z repozytorium dystrybucji.
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/beru/llvm_root/lib/

~/llvm_root/bin/clang++ main.cpp -std=c++14 -stdlib=libc++ \
    -nodefaultlibs -lc++ -lc++abi -lm -lc -lgcc_s -lgcc \
    -I/home/beru/llvm_root/include/c++/v1/ \
    -L/home/beru/llvm_root/lib/

27 września 2015

[clang] Dynamiczna i statyczna analiza programów

Coś co chciałem wypróbować już bardzo dawno temu, a mianowicie narzędzie do statycznej i dynamicznej analizy kodu/programów. Zachęcił mnie do tego przede wszystkim wykład z GoginNative 2013: The Care and Feeding of C++’s Dragons (0:51h, 1:05h). Najwyraźniej funkcjonalność którą dostarcza kompilator (clang), została wydzielona do osobnego narzędzia jakim jest scan-build, choć nie eksperymentowałem z tym rozwiązaniem za dużo. Kilka przydatnych linków:

Clang Static Analyzer

Narzędzie korzysta ze zbioru reguł, które wciąż się rozrastają. Można skorzystać z domyślnych ustawień lub samodzielnie wskazać pod jakim kątem ma być przeskanowany kod. Przykład z dzieleniem przez zero, zaczerpnięty z dokumentacji.
#include <iostream>

using namespace std;

int test(int z) {
    if (z == 0)
        return 1 / z;
    return -1;
}

int main() {
    int i = 63;
    std::cin >> i;
    test(i);
    return 0;
}
W pierwszych dwóch linijkach dwa alternatywne wywołania:
clang++ -std=c++14 --analyze -Xanalyzer -analyzer-output=text main.cpp
# inna wersja
clang++ -std=c++14 --analyze -Xanalyzer -analyzer-checker=core.DivideZero -Xanalyzer -analyzer-output=text main.cpp
Wynik:
main.cpp:7:18: warning: Division by zero
        return 1 / z;
                 ^
main.cpp:14:10: note: Passing value via 1st parameter 'z'
    test(i);
         ^
main.cpp:14:5: note: Calling 'test'
    test(i);
    ^~~~~~~
main.cpp:6:9: note: Assuming 'z' is equal to 0
    if (z == 0)
        ^~~~~~
main.cpp:6:5: note: Taking true branch
    if (z == 0)
    ^
main.cpp:7:18: note: Division by zero
        return 1 / z;
               ~~^~~
1 warning generated.
Niestety przykłady które stworzyłem samodzielnie, już nie podają takich wyników. Testy przeprowadzałem na wersji 3.6.0 oraz 3.8.0. No nic, rozwiązanie i tak jest interesujące, liczę że w przyszłości narzędzie się rozwinie.
#include <iostream>

using namespace std;

int test2(int z) {
    return 1 / z;
}

int test3(int z) {
    if (z > 15)
        return 1;
    return 1 / z;
}

int main() {
    int i = 63;
    std::cin >> i;

    test2(i);
    test3(i);

    return 0;
}

Sanitizer

Innym narzędziem, które istnieje w clang-u jest sanitizer, a właściwie ich kolekcja. Analiza przeprowadzana jest dynamicznie, więc najlepsze efekty można uzyskać, jeżeli kod był tworzony razem z testami. Sprawdziłem jedynie AddressSanitizer, ale w dokumentacji można odnaleźć informacji o innych:
#include <iostream>

using namespace std;

int main() {
    char tab1[5];
    for(int i = 0; i <= 5; ++i)
        tab1[i] = 'x';
    return 0;
}

Wywołanie:
clang++ -std=c++14 -fsanitize=address main.cpp
./a.out
Rezultat:
=================================================================
==5748==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffd47e300b5 at pc 0x0000004dc5ad bp 0x7ffd47e30010 sp 0x7ffd47e30008
WRITE of size 1 at 0x7ffd47e300b5 thread T0
    #0 0x4dc5ac  (/home/beru/synetizer_test/a.out+0x4dc5ac)
    #1 0x7fcefcca4a3f  (/lib/x86_64-linux-gnu/libc.so.6+0x20a3f)
    #2 0x4350e8  (/home/beru/synetizer_test/a.out+0x4350e8)

Address 0x7ffd47e300b5 is located in stack of thread T0 at offset 53 in frame
    #0 0x4dc32f  (/home/beru/synetizer_test/a.out+0x4dc32f)

  This frame has 3 object(s):
    [32, 36) ''
    [48, 53) 'tab1' <== Memory access at offset 53 overflows this variable
    [80, 84) 'i'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
      (longjmp and C++ exceptions *are* supported)
Shadow bytes around the buggy address:
  0x100028fbdfc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbdfd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbdfe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbdff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbe000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x100028fbe010: f1 f1 f1 f1 04 f2[05]f2 f2 f2 04 f3 00 00 00 00
  0x100028fbe020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbe030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbe040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbe050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100028fbe060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==5748==ABORTING

26 września 2015

Windows Kernel Exploitation - warsztat

Kilka interesujących artykułów, które znalazłem, a dotyczyły one wykorzystanie podatności w jądrze systemu Windows. Punktem wejścia jest tutaj przede wszystkim interakcja z API win32k.sys. Podstawowe funkcje:
  • zawiera windows manager
    • kontroluje wyświetlania okien
    • zarządza wyjściem na ekran
    • kolekcjonuje dane wejściowe z klawiatury, myszy itd.,
    • woła zdefiniowane przez aplikacja hook-i
    • przekazuje mesgi użytkownika do aplikacji
    • zarządza obiektami użytkownika
  • zawiera graphics device interface (GDI) - bibilioteki funkcji graficznych
    • rysowanie i manipulowanie obiektami: liniami, tekstem i figurami
    • zarządza obiektami (GDI) takimi jak brushes, pens, DCs itp.
    • dostarcza API dla sterowników viedeo/drukarki
W sumie po stronie użytkownika można wywołać ponad 800 funkcji. Warto też wiedzieć, że każdy typ obiektu zdefiniowany jest prze unikalną strukturę (np. win32k!tagWND, win32k!tagCURSOR, itp.).

Szczerze mówiąc najbardziej zainteresował mnie warsztat, jakim posługują się autorzy artykułów. Nie kierowałem się żadnym kluczem, po znalezieniu pierwszego artykuły przeskakiwałem do przypisów i tym sposobem stworzyłem sobie małą listę. Nie zagłębiałem się też zbyt mocno w samą tematykę, więc jest to coś do czego będzie warto wrócić.
Ostatnia źródło informacji to bardzo fajna prezentacja z konferencji Blackhat 2011 ("Kernel Attacks Through User- Mode Callbacks") i opowiada o samej architekturze systemu i klasach błędów jakie można w nim napotkać: use-after-free (alokacja i niszczenie obiektów np. znajdujących się w menu), null-pointer-dereference. Więcej około 30-35 minuty.


3 września 2015

[C++11] Muteksy i współdzielenie danych między wątkami

Wstęp

Biblioteka standardowa C++11 wprowadza klasę std::lock_guard typu RAII implementującą idiom muteksu (blokuje muteks w konstruktorze i zwalnia go w destruktorze). Stosując muteksy należy przyswoić sobie dwie ważne zasady:
  1. Nie wolno zwracać lub zapisywać wskaźników lub referencji poza zasięgiem lock-a (blokady).
  2. Należy blokować zasoby tylko przez minimalny okres czasu, potrzebny do wykonania danej operacji.
Przykład poniżej pokazuje wykorzystanie std::lock_guard.
#include <iostream>
#include <string>
#include <thread>
#include <mutex>

using namespace std;

class Storage {
private:
    std::mutex interanl_mutex;
    std::string buff;

public:
    void increment(const std::string id) {
        std::lock_guard<std::mutex> guard(interanl_mutex);
        buff += id;
    }

    void show() {
        std::lock_guard<std::mutex> guard(interanl_mutex);
        std::cout << buff << endl;
    }
};

void update(const std::string id, int loop_counter, Storage& storage) {
    for (int i = 0; i < loop_counter; ++i) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        storage.increment(id);
    }
}

int main() {
    Storage s;
    std::thread t1{update, std::string("a"), 2, std::ref(s)};
    std::thread t2{update, std::string("b"), 3, std::ref(s)};

    t1.join();
    t2.join();

    s.show();
    return 0;
}
Wynik:
ababb

Deadlock - zakleszczenie

Z samym zagadnieniem można zapoznać się tutaj:
Kilka zasad dzięki, którym można uniknąć tego rodzaju problemów:
  1. Nie wolno czekać na inny wątek jeżeli istnieje szansa, że to on może czekać na nas.
  2. Nie wolno zakładać blokad, jeżeli jakaś została już założona. Jeżeli wątek potrzebuje kilku muteksów należy wykonać blokowanie jako pojedynczą operację (stosując np. std::lock)
  3. Jeżeli tworzona jest biblioteka, nie należy wołać kodu użytkownika trzymając jednocześnie blokadę na muteksach. Nigdy nie można być pewnym, czy kod użytkownika też nie założy swojej własnej blokady.
  4. Jeżeli nie można skorzystać z std::lock do założenia kilku blokad jednocześnie, należy pamiętać by blokady zakładać zawsze w tej samej kolejności.
  5. Nie wolno zakładać blokad, jeżeli kod posiada już blokadę na muteksa z niższego poziomu (można stworzyć hierarchical_mutex, który nie jest częścią standardu ale jest łatwy w implementacji [1])
W tym miejscu należy wspomnieć o nowej klasie std::unique_lock, którą można stosować tam gdzie należy odroczyć blokowanie muteksów, albo gdy istnieje potrzeba przetransferowania własności do innego obiektu. Ma ona podobny konstruktor do std::lock_guard.
Drugi parametr konstruktora może przyjmować kilka interesujących parametrów:
  • std::adopt_lock jest używany by zaznaczyć, że muteks został już zablokowany i std::lock_guard albo std::unique_lock powinien tylko zaadoptować własność, zamiast próbować go jeszcze raz blokować.
  • std::defer_lock występuje tylko dla std::unique_lock i mówi o tym że muteks nie powinien zostać zablokowany przez konstruktor. Można tą operację wykonać później wołając np. funkcję std::lock.
Dwa przykłady jak może wyglądać zakładanie kilku blokad jednocześnie:
std::mutex mutex1;
std::mutex mutex2;

std::lock(mutex1, mutex2);
std::lock_guard<std::mutex> lock1(mutex1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mutex2, std::adopt_lock);
Alternatywa z wykorzystaniem std::unique_lock:
std::mutex mutex1;
std::mutex mutex2;

std::unique_lock<std::mutex> lock1(mutex1, std::defer_lock);
std::unique_lock<std::mutex> lock2(mutex2, std::defer_lock);
std::lock(lock1, lock2);

Inicjowanie zasobów współdzielonych przez wątki

Często zachodzi potrzeba zainicjowania zmiennej tylko przez jeden z wątków. Zamiast samodzielnie tworzyć kod blokujący zasób i sprawdzający czy został on zainicjowany, biblioteka standardowa wprowadza specjalny mechanizm w postaci std::call_flag i std::call_once, które pozwalają na wykonanie takiej operacji (inicjowania) dokładnie raz w bezpieczny sposób.

W C++11 istnieje konieczność stosowania std::ref w przypadku argumentów do funkcji inicjującej, inaczej kompilator zaprotestuje. Zostało to naprawione w C++17.
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>

using namespace std;

std::mutex vec_mutex;
std::once_flag vec_flag;

void show(std::string description, std::vector<int>& vec) {
    std::cout << description;
    for(const auto& v : vec)
        std::cout << v << " ";
    std::cout << std::endl;
}

void init(std::vector<int>& vec) {
    for(auto& v : vec)
        v = 0;
    show("Initialize (call_once):    ", vec);
}

void update(std::vector<int>& vec, int index) {
//  std::call_once(vec_flag, init, std::ref(vec));   // Działa w C++11
    std::call_once(vec_flag, init, vec);             // Działa w C++17
    std::lock_guard<std::mutex> guard(vec_mutex);
    vec[index] += 1;
}

int main() {
    std::vector<int> vec = {9, 9, 9};
    show("Creation (in main thread): ", vec);

    std::thread t1{update, std::ref(vec), 0};
    std::thread t2{update, std::ref(vec), 2};

    t1.join();
    t2.join();

    show("All thread finish:         ", vec);

    return 0;
}
Wynik:
Creation (in main thread): 9 9 9 
Initialize (call_once):    0 0 0 
All thread finish:         1 0 1 

Bibliografia

  • [1] Anthony Williams: C++ Concurency in Action. USA Manning publications Co., 2012. Rozdział 3, str. 33.

2 sierpnia 2015

[python] WinAppDbg

Natchniony bardzo fajnym wykładem Wesley-a McGrew - Instrumenting Point-of-Sale Malware (DEF CON 22), przyjrzałem się bibliotece WinAppDbg.



Biblioteka daje możliwość wygodnego analizowania, debugowania i manipulowania procesami w środowisku Windows. Niestety od wersji 1.5 python 3 nie jest już oficjalnie wspierany, pozostaje więc wersja dla python-a 2. Na stronie głównej projektu, autorzy podają także pokaźny zbiór linków, do podobnych projektów. Najciekawszą rzeczą jest bardzo bogata dokumentacja.
Instalacja za pomocą pip.
pip install winappdbg
I przykładowy program, nic nadzwyczajnego, moja modyfikacja drugiego przykładu z innym/lepszym formatowaniem.
from winappdbg import System

# Create a system snaphot.
system = System()

print('PID:    Exe Name:')
print('-----------------')
for process in system:
    space = ' ' * (8 - len(str(process.get_pid())) - 1)
    print(str(process.get_pid()) + ':' + space + str(process.get_filename()))