Многопоточност с вградено асемблиране и достъп до c променлива

Използвам вградено асемблиране, за да конструирам набор от пароли, които ще използвам за груба сила срещу даден хеш. Използвах този уебсайт като справка за изграждането на паролите.

Това работи безупречно в среда с една нишка. Той произвежда безкрайно количество увеличаващи се пароли.

Тъй като имам само основни познания за asm, разбирам идеята. Gcc използва ATT, така че компилирам с -masm=intel

По време на опита за многонишкова програма осъзнавам, че този подход може да не работи.
Следният код използва 2 глобални C променливи и предполагам, че това може да е проблемът.

__asm__("pushad\n\t"
    "mov edi, offset plaintext\n\t" <---- global variable
    "mov ebx, offset charsetTable\n\t" <---- again
    "L1: movzx eax, byte ptr [edi]\n\t"
    "    movzx eax, byte ptr [charsetTable+eax]\n\t"
    "    cmp al, 0\n\t"
    "    je L2\n\t"
    "    mov [edi],al\n\t"
    "    jmp L3\n\t"
    "L2: xlat\n\t"
    "    mov [edi],al\n\t"
    "    inc edi\n\t"
    "    jmp L1\n\t"
    "L3: popad\n\t");

Той произвежда недетерминиран резултат в променливата за обикновен текст.

Как мога да създам заобиколно решение, така че всяка нишка да има достъп до собствената си променлива в обикновен текст? (Ако това е проблемът...).

Опитах се да модифицирам този код, за да използвам разширен асембли, но всеки път се провалях. Вероятно поради факта, че всички уроци използват ATT синтаксис.

Наистина ще съм благодарен за всякаква помощ, тъй като съм блокиран вече няколко часа :(

Редактиране: Изпълнението на програмата с 2 нишки и отпечатването на съдържанието на обикновен текст веднага след asm инструкцията, произвежда:
b
b
d
d
f
f
...

Редактиране 2:

pthread_create(&thread[i], NULL, crack, (void *) &args[i]))
[...]
void *crack(void *arg) {
struct threadArgs *param = arg;
struct crypt_data crypt; // storage for reentrant version of crypt(3)

char *tmpHash = NULL;

size_t len = strlen(param->methodAndSalt);
size_t cipherlen = strlen(param->cipher);

crypt.initialized = 0;

for(int i = 0; i <= LIMIT; i++) {
    // intel syntax      
    __asm__ ("pushad\n\t"
    //mov edi, offset %0\n\t"
    "mov edi, offset plaintext\n\t"
    "mov ebx, offset charsetTable\n\t"
    "L1: movzx eax, byte ptr [edi]\n\t"
    "    movzx eax, byte ptr [charsetTable+eax]\n\t"
    "    cmp al, 0\n\t"
    "    je L2\n\t"
    "    mov [edi],al\n\t"
    "    jmp L3\n\t"
    "L2: xlat\n\t"
    "    mov [edi],al\n\t"
    "    inc edi\n\t"
    "    jmp L1\n\t"
    "L3: popad\n\t");

    tmpHash = crypt_r(plaintext, param->methodAndSalt, &crypt);
    if(0 == memcmp(tmpHash+len, param->cipher, cipherlen)) {
        printf("success: %s\n", plaintext);
        break;
    }
}
return 0;
} 

person nce    schedule 18.09.2011    source източник
comment
Ще трябва да заключите достъпа до вашите plaintext и charsetTable променливи, ако две или повече нишки имат достъп до тях. Трудно е да се каже от това, което сте публикували, дали това наистина е проблемът. Можете ли да ни покажете кода за нишки?   -  person Tony The Lion    schedule 18.09.2011
comment
Трябва наистина да декларирате своя asm като volatile, тъй като в противен случай GCC може да го премести. Освен това трябва да имате memory в списъка за изтриване. Вижте тук.   -  person user786653    schedule 18.09.2011
comment
Опитайте се да избягвате глобалните променливи. В този случай лесният начин би бил използването на стекови променливи. Друга, по-сложна възможност е използването на локални променливи на нишка.   -  person Gunther Piez    schedule 19.08.2012
comment
BTW, asm кодът е бавен. По-конкретно инструкцията xlat (която може би е била най-бързият начин преди 20 години) трябва да се избягва.   -  person Gunther Piez    schedule 19.08.2012


Отговори (2)


Тъй като вече използвате pthreads, друга опция е да превърнете променливите, които са модифицирани от няколко нишки, в променливи за всяка нишка (данни, специфични за нишка). Вижте pthread_getspecific страница с ръководство на OpenGroup. Начинът, по който това работи е като:

В основната нишка (преди да създадете други нишки), направете:

static pthread_key_y tsd_key;
(void)pthread_key_create(&tsd_key);    /* unlikely to fail; handle if you want */

и след това във всяка нишка, където използвате променливите plaintext / charsetTable (или повече такива), направете:

struct { char *plainText, char *charsetTable } *str =
    pthread_getspecific(tsd_key);

if (str == NULL) {
    str = malloc(2 * sizeof(char *));
    str.plainText = malloc(size_of_plaintext);
    str.charsetTable = malloc(size_of_charsetTable);
    initialize(str.plainText);          /* put the data for this thread in */
    initialize(str.charsetTable);       /* ditto */
    pthread_setspecific(tsd_key, str);
}
char *plaintext = str.plainText;
char *charsetTable = str.charsetTable;

Или създайте/използвайте няколко ключа, по един за такава променлива; в този случай не получавате str контейнер / двойна индиректност / допълнителен malloc.

Синтаксисът на асемблиране на Intel с gcc inline asm е, хм, не е страхотен; по-специално, определянето на входно/изходни операнди не е лесно. Мисля, че за да накарате това да използва механизма pthread_getspecific, трябва да промените кода си, за да направите:

__asm__("pushad\n\t"
    "push tsd_key\n\t"               <---- threadspecific data key (arg to call)
    "call pthread_getspecific\n\t"   <---- gets "str" as per above
    "add esp, 4\n\t"                 <---- get rid of the func argument
    "mov edi, [eax]\n\t"             <---- first ptr == "plainText"
    "mov ebx, [eax + 4]\n\t"         <---- 2nd ptr == "charsetTable"
    ...

По този начин става без заключване, за сметка на използването на повече памет (една таблица с обикновен текст / charsetTable на нишка) и за сметка на допълнително извикване на функция (до pthread_getspecific()). Освен това, ако направите горното, уверете се, че free() специфичните данни за всяка нишка чрез pthread_atexit(), или в противен случай ще изтече информация.

Ако вашата функция е бърза за изпълнение, тогава заключването е много по-просто решение, защото не се нуждаете от цялата настройка/почистване на специфични за нишката данни; ако функцията е или бавна, или много често се извиква, заключването все пак ще се превърне в тясно място - в този случай излишните разходи за памет/достъп за TSD са оправдани. Вашият пробег може да варира.

person FrankH.    schedule 19.09.2011
comment
това изглежда обещаващо. Определено ще го пробвам. Благодаря за страхотния и подробен пост. оценявам го! - person nce; 20.09.2011
comment
Маркирах това като мое решение, тъй като това е директен отговор на моя въпрос, отървах се от глобалната променлива. Измерванията на скоростта показват, че дори е малко по-бърз. Благодаря отново - person nce; 20.09.2011
comment
да, може да е по-бързо, тъй като това е случай без споделени данни (възпроизвеждате променливата(ите) веднъж на нишка); по-висок отпечатък на паметта срещу по-добър паралелизъм. - person FrankH.; 22.09.2011

Защитете тази функция с mutex извън вградения асемблиращ блок.

person Alex F    schedule 18.09.2011
comment
помислих си за това. Сега го пробвах. Изглежда, че работи. Но предполагам, че това означава голяма загуба на скорост. Трябва да заключа plaintext, след което да копирам съдържанието, преди да освободя заключването, и всъщност да хеширам обикновения текст. Но изглежда, че няма друг избор. Благодаря :) - person nce; 18.09.2011