Многопоточность со встроенной сборкой и доступом к переменной c

Я использую встроенную сборку для создания набора паролей, которые я буду использовать для грубой силы против заданного хэша. Я использовал этот веб-сайт в качестве эталона для построения паролей.

Это работает безупречно в однопоточной среде. Он производит бесконечное количество увеличивающихся паролей.

Поскольку у меня есть только базовые знания ассемблера, я понимаю эту идею. 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.

Я был бы очень признателен за любую помощь, так как я застрял уже несколько часов :(

Изменить. Запуск программы с двумя потоками и печать содержимого открытого текста сразу после инструкции 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 в clobberlist. См. здесь.   -  person user786653    schedule 18.09.2011
comment
Старайтесь избегать глобальных переменных. В этом случае простым способом будет использование переменных стека. Другая, более сложная возможность — использование локальных переменных потока.   -  person Gunther Piez    schedule 19.08.2012
comment
Кстати, код 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, хм, не очень хорош; в частности, указать операнды ввода/вывода непросто. Я думаю, чтобы заставить это использовать механизм 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

Защитите эту функцию с помощью мьютекса за пределами встроенного блока сборки.

person Alex F    schedule 18.09.2011
comment
я думал об этом. Теперь я попробовал это. Кажется, это работает. Но я предполагаю, что это означает большую потерю скорости. Я должен заблокировать plaintext, затем скопировать содержимое, прежде чем снять блокировку и фактически хешировать открытый текст. Но похоже, что другого выхода нет. Благодарность :) - person nce; 18.09.2011