Ассмемблер вставками

Тема в разделе "WASM.BEGINNERS", создана пользователем dcc0, 22 дек 2022.

  1. dcc0

    dcc0 Member

    Публикаций:
    2
    Регистрация:
    22 дек 2022
    Сообщения:
    60
    Доброго! Ассемблер в общем не знаю, но код, который генерирует gcc иногда посматриваю, ради расширения кругозора. Попробовал сделать такие вставки в код (количество инструкций, кстати, если генерировать gcc с опцией -S, увеличилось).
    Но интересно, будет ли работать на другой машине такой код? (синтаксис, как я понял, AT&T).
    Какие регистры дёргать, подбирал, скажем так, методом тыка.
    Онлайн компиляторы запускают. Машина у меня Atom 64b с Linux.

    Код (Text):
    1.  
    2. /*Порождение перестановок*/
    3. #include <stdio.h>
    4. int main(){
    5.         char a[] = "4321";  /*array*/
    6.            int i, j, f;
    7.            asm("movl    $24, -20(%rbp);"); /*f=24*/
    8.            char c;
    9.           while (f--) {
    10.           printf("%s\n", a);
    11.            asm ("movl    $1, -28(%rbp);"); /*i=1*/
    12.           while(a[i] >= a[i-1]) i++;
    13.            asm ("movl    $0, -24(%rbp);"); /*j=0*/
    14.            while(a[j] <= a[i])j++;
    15.       c=a[j];
    16.       a[j]=a[i];
    17.       a[i]=c;
    18. i--;
    19. for (j = 0; j < i; i--, j++) {
    20.   c = a[i];
    21.   a[i] = a[j];
    22.   a[j] = c;
    23.       }
    24.    }
    25. }
    26.  
     
  2. ormoulu

    ormoulu Well-Known Member

    Публикаций:
    0
    Регистрация:
    24 янв 2011
    Сообщения:
    1.206
    Скомпилированый - будет работать на другой машине той же архитектуры, архитектура у вас x64. Ну и ОС должна позволять запускать ваш elf.

    Сишный будет работать если: 1) машина x64 2) компилятор поддерживает синтаксис 3) компилятор так же распологает на стеке локальные переменные, тот же пролог процедуры и т.д.
    Кстати возможно ваш код перестанет работать, если собрать с другими опциями компилятора, т.к. могут измениться соглашения, расположение и выравнивание переменных и прочая.
     
  3. dcc0

    dcc0 Member

    Публикаций:
    2
    Регистрация:
    22 дек 2022
    Сообщения:
    60
    Благодаю. Понял. Примерно так и предположил.
    Ведь, как я понял, я напрямую изменяю значение стека.
    Разница в скорости, как я понимаю, тоже не будет если оставить i=1 или вставку на Ассемблере. Или будет?!
    В общем у такого кода слабая переносимость, но если надо немножко запутать код, то как раз.
     
  4. f13nd

    f13nd Well-Known Member

    Публикаций:
    0
    Регистрация:
    22 июн 2009
    Сообщения:
    2.009
    Ты же говоришь, что смотришь выхлоп гцц. И как, без -O3 там совсем не треш и угар получается?

    ЗЫ: когда-то развлекался с гцц, было потешно. Оказалось без -O3 он совсем плох и даже с ним не очень.
     
    Последнее редактирование: 22 дек 2022
  5. Intro

    Intro Active Member

    Публикаций:
    0
    Регистрация:
    29 авг 2009
    Сообщения:
    607
    Эти вставки плохо оптимизируются, по мне или полностью на ассемблере, или лучше без него, ну может отдельно в dll запихнуть свой код на асме.
     
  6. dcc0

    dcc0 Member

    Публикаций:
    2
    Регистрация:
    22 дек 2022
    Сообщения:
    60
    Разница очень большая по количеству строк. С -O3 всего 37 строк. Без этой опции 121 строка. Это если применять к коду с встаками на Асме.
    Если оставить присвоение переменным на C, то чистый с код с опцией -O3 выдаёт 134 строки.
    P.S.
    Если сокращение количества строк кода считать оптимизацией (я почему-то верю, что так и надо считать. Или я не прав?! (ибо количество операций и время)), то эти вставки имеют смысл, код сократился почти в три раза.
    Всем большое спасибо!
     
    Последнее редактирование: 22 дек 2022
  7. f13nd

    f13nd Well-Known Member

    Публикаций:
    0
    Регистрация:
    22 июн 2009
    Сообщения:
    2.009
    Количество строк не показатель, там же бред местами получается, если разбирать. Такая же портянка из паттернов, как у деда Борланда.
     
  8. dcc0

    dcc0 Member

    Публикаций:
    2
    Регистрация:
    22 дек 2022
    Сообщения:
    60
    C -O3 и вставками:
    Код (Text):
    1.     .file    "perm_asm.c"
    2.     .text
    3.     .section    .text.startup,"ax",@progbits
    4.     .p2align 4
    5.     .globl    main
    6.     .type    main, @function
    7. main:
    8. .LFB23:
    9.     .cfi_startproc
    10.     subq    $24, %rsp
    11.     .cfi_def_cfa_offset 32
    12.     movq    %fs:40, %rax
    13.     movq    %rax, 8(%rsp)
    14.     xorl    %eax, %eax
    15.     movl    $825373492, 3(%rsp)
    16.     movb    $0, 7(%rsp)
    17. #APP
    18. # 6 "perm_asm.c" 1
    19.     movl    $24, -20(%rbp);
    20. # 0 "" 2
    21. #NO_APP
    22.     movq    8(%rsp), %rax
    23.     subq    %fs:40, %rax
    24.     jne    .L24
    25.     xorl    %eax, %eax
    26.     addq    $24, %rsp
    27.     .cfi_remember_state
    28.     .cfi_def_cfa_offset 8
    29.     ret
    30. .L24:
    31.     .cfi_restore_state
    32.     call    __stack_chk_fail@PLT
    33.     .cfi_endproc
    34. .LFE23:
    35.     .size    main, .-main
    36.     .ident    "GCC: (Gentoo 11.3.0 p7) 11.3.0"
    37.     .section    .note.GNU-stack,"",@progbits
    Без -O3 и без вставок:

    Код (Text):
    1.     .file    "perm.c"
    2.     .text
    3.     .globl    main
    4.     .type    main, @function
    5. main:
    6. .LFB0:
    7.     .cfi_startproc
    8.     pushq    %rbp
    9.     .cfi_def_cfa_offset 16
    10.     .cfi_offset 6, -16
    11.     movq    %rsp, %rbp
    12.     .cfi_def_cfa_register 6
    13.     subq    $32, %rsp
    14.     movq    %fs:40, %rax
    15.     movq    %rax, -8(%rbp)
    16.     xorl    %eax, %eax
    17.     movl    $825373492, -13(%rbp)
    18.     movb    $0, -9(%rbp)
    19.     movl    $24, -20(%rbp)
    20.     jmp    .L2
    21. .L9:
    22.     leaq    -13(%rbp), %rax
    23.     movq    %rax, %rdi
    24.     call    puts@PLT
    25.     movl    $1, -28(%rbp)
    26.     jmp    .L3
    27. .L4:
    28.     addl    $1, -28(%rbp)
    29. .L3:
    30.     movl    -28(%rbp), %eax
    31.     cltq
    32.     movzbl    -13(%rbp,%rax), %edx
    33.     movl    -28(%rbp), %eax
    34.     subl    $1, %eax
    35.     cltq
    36.     movzbl    -13(%rbp,%rax), %eax
    37.     cmpb    %al, %dl
    38.     jge    .L4
    39.     movl    $0, -24(%rbp)
    40.     jmp    .L5
    41. .L6:
    42.     addl    $1, -24(%rbp)
    43. .L5:
    44.     movl    -24(%rbp), %eax
    45.     cltq
    46.     movzbl    -13(%rbp,%rax), %edx
    47.     movl    -28(%rbp), %eax
    48.     cltq
    49.     movzbl    -13(%rbp,%rax), %eax
    50.     cmpb    %al, %dl
    51.     jle    .L6
    52.     movl    -24(%rbp), %eax
    53.     cltq
    54.     movzbl    -13(%rbp,%rax), %eax
    55.     movb    %al, -29(%rbp)
    56.     movl    -28(%rbp), %eax
    57.     cltq
    58.     movzbl    -13(%rbp,%rax), %edx
    59.     movl    -24(%rbp), %eax
    60.     cltq
    61.     movb    %dl, -13(%rbp,%rax)
    62.     movl    -28(%rbp), %eax
    63.     cltq
    64.     movzbl    -29(%rbp), %edx
    65.     movb    %dl, -13(%rbp,%rax)
    66.     subl    $1, -28(%rbp)
    67.     movl    $0, -24(%rbp)
    68.     jmp    .L7
    69. .L8:
    70.     movl    -28(%rbp), %eax
    71.     cltq
    72.     movzbl    -13(%rbp,%rax), %eax
    73.     movb    %al, -29(%rbp)
    74.     movl    -24(%rbp), %eax
    75.     cltq
    76.     movzbl    -13(%rbp,%rax), %edx
    77.     movl    -28(%rbp), %eax
    78.     cltq
    79.     movb    %dl, -13(%rbp,%rax)
    80.     movl    -24(%rbp), %eax
    81.     cltq
    82.     movzbl    -29(%rbp), %edx
    83.     movb    %dl, -13(%rbp,%rax)
    84.     subl    $1, -28(%rbp)
    85.     addl    $1, -24(%rbp)
    86. .L7:
    87.     movl    -24(%rbp), %eax
    88.     cmpl    -28(%rbp), %eax
    89.     jl    .L8
    90. .L2:
    91.     movl    -20(%rbp), %eax
    92.     leal    -1(%rax), %edx
    93.     movl    %edx, -20(%rbp)
    94.     testl    %eax, %eax
    95.     jne    .L9
    96.     movl    $0, %eax
    97.     movq    -8(%rbp), %rdx
    98.     subq    %fs:40, %rdx
    99.     je    .L11
    100.     call    __stack_chk_fail@PLT
    101. .L11:
    102.     leave
    103.     .cfi_def_cfa 7, 8
    104.     ret
    105.     .cfi_endproc
    106. .LFE0:
    107.     .size    main, .-main
    108.     .ident    "GCC: (Gentoo 11.3.0 p7) 11.3.0"
    109.     .section    .note.GNU-stack,"",@progbits
    110.  
    P.S.
    Кстати, nasm не смог собрать первый код.
    Куча вот таких ошибок:
    Код (Text):
    1. perm_asm.asm:2: warning: label alone on a line without a colon might be in error [-w+label-orphan]
    2. perm_asm.asm:3: error: parser: instruction expected
    3. perm_asm.asm:4: error: parser: instruction expected
    4. perm_asm.asm:5: error: parser: instruction expected
    5. perm_asm.asm:6: error: parser: instruction expected
     
    Последнее редактирование: 22 дек 2022
  9. f13nd

    f13nd Well-Known Member

    Публикаций:
    0
    Регистрация:
    22 июн 2009
    Сообщения:
    2.009
    А куда у тебя вся полезная нагрузка с -O3 делась? Только f инициализируется, дальше просто убирается SEH и выход.
    Код (Text):
    1.     movq    %fs:40, %rax
    2.     movq    %rax, 8(%rsp)
    3.     xorl    %eax, %eax
    4.     movl    $825373492, 3(%rsp)
    5.     movb    $0, 7(%rsp)
    6. #APP
    7. # 6 "perm_asm.c" 1
    8.     movl    $24, -20(%rbp);
    9. # 0 "" 2
    10. #NO_APP
    11.     movq    8(%rsp), %rax
    12.     subq    %fs:40, %rax
    13.     jne .L24
    14.     xorl    %eax, %eax
    15.     addq    $24, %rsp
    16.     .cfi_remember_state
    17.     .cfi_def_cfa_offset 8
    18.     ret
    19. .L24:
     
  10. dcc0

    dcc0 Member

    Публикаций:
    2
    Регистрация:
    22 дек 2022
    Сообщения:
    60
    Вот и выяснилось! Рано я обрадовался оптимизации! И -O3, и -O2 дают на выходе segfault.
    Оптимизировал всё, в буквальном смысле.
     
  11. f13nd

    f13nd Well-Known Member

    Публикаций:
    0
    Регистрация:
    22 июн 2009
    Сообщения:
    2.009
    Я хз можно ли, не можно, но лучше используй не адрес переменной, а именно ее метку во вставках. Похоже оптимизация убрала все лишнее и неиспользуемое.
     
  12. dcc0

    dcc0 Member

    Публикаций:
    2
    Регистрация:
    22 дек 2022
    Сообщения:
    60
    Переход в смысле?
    Код (Text):
    1. jmp .L3
    К примеру? Не получится в случае с этим кодом, скорее всего. Или что-то другое имеется в виду?
     
  13. Rel

    Rel Well-Known Member

    Публикаций:
    2
    Регистрация:
    11 дек 2008
    Сообщения:
    5.314
    В ассемблерных вставках в гцц есть списки зависимых переменных, оптимизатор цэ удаляет неиспользуемые переменные совершенно логичным образом до уровня ассемблера, очень большое количество оптимизаций происходит на уровне SSA-представления.
     
  14. f13nd

    f13nd Well-Known Member

    Публикаций:
    0
    Регистрация:
    22 июн 2009
    Сообщения:
    2.009
    В смысле имя переменной f использовать, а не адресовать по rbp. Либо найти способ директивами нужные переменные вручную пометить как используемые.
     
  15. Rel

    Rel Well-Known Member

    Публикаций:
    2
    Регистрация:
    11 дек 2008
    Сообщения:
    5.314
    https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html - можно указать входные переменные, выходные переменный, регистры, которые вставка изменила и метки.
     
  16. ormoulu

    ormoulu Well-Known Member

    Публикаций:
    0
    Регистрация:
    24 янв 2011
    Сообщения:
    1.206
    Можно ещё безо всяких вставок взять адрес любой переменной на стеке и по смещениям модифицировать что угодно, включая адрес возврата. Только надо учитывать выравнивания.
     
  17. aa_dav

    aa_dav Active Member

    Публикаций:
    0
    Регистрация:
    24 дек 2008
    Сообщения:
    479
    Правильно пользоваться ассемблерными вставками на GCC - это немножечко искусство.
    GCC вообще по меньшей мере исторически устроен как бы так, что генерирует не объектный код (.o), а исходник для GNU Assembler (.s) и вот он уже компилируется в .o. Эту фазу, однако, давно уже запрятали под капот и её наличие даже никак не отсвечивает пока не дашь ключ -S. Но под капотом оно всё так и осталось, в этом легко убедится как раз скомпилировав полученный .s - результирующий бинарник будет до байта совпадать с тем что получается и без -S.
    В результате получается интересная фигня - если gcc видит в исходнике inline asm, то он собственно никак не компилирует его, а просто вставляет его текст в ассемблерный код который нагенерировал сам, и отдаёт уже ассемблеру (GNU ASM) всё полученное - если будут ошибки в тексте, то о них тебе будет сообщать уже GNU Asm, а с ключом -S можно будет легко увидеть сущность происходящей текстоподстановки.
    Казалось бы такая тупая и прямолинейная техника должна быть неудобной или негибкой, но удивительно насколько всё обстоит прямо противоположным образом - то как в gcc реализован inline asm напротив даёт огромный спектр возможностей и оптимизационного потенциала. Удивительно, но ему нередко проигрывает в этом встроенный в MSVC 32 асм MS, несмотря на то, что он бесшовно интегрирован в синтаксис языка.
    Однако при всём при этом поначалу и зачастую будет возникать ощущение, что способ GCC негибок и чего то в нём не получается сделать, ибо вот ну не хватает возможностей. Это ощущение ложно - нужно или еще хорошенько подумать или спросить совета на форуме. Как правило решение находится на поверхности, просто руководства явно в него не тыкают и оно, поэтому, не отсвечивает своей очевидностью.
    Искусство тут крутится вокруг того чтобы правильно "сшить" свою текстовую асмоподстановку с окружающей программой через параметры clobber list и т.п. Мы должны явно сообщить компилятору какие у нас входные данные, какие у нас выходные данные и какие вещи в процессе нашего ассемблера будут испорчены и тогда он сам раскидает всё наилучшим образом, а главное - не даст выстрелить себе в ногу.
    При этом есть всякие нюансы - например не стоит в самом начале своего асмокода грузить в регистр переменную - это плохой и негибкий вариант. Надо через параметры асмоподстановки сообщить компилятору, что тебе нужно чтобы содержимое переменной оказалось в этом регистре - тогда он максимально оптимально сошьёт код так что в оптимизированном варианте программе расчёты с этой переменной уже к моменту асмоподстановки велись в этом регистре и не будет вообще никакого пенальти на перекладывания. Если просто документацию почитать, то куча таких моментов просто неочевидна и можно городить как неоптимальный так и попросту невалидный код - если оптимизатор не понимает, что ты "поломал" какие то регистры, то всё полетит к чертям. Код из нуль-поста, например, неправилен и перестанет работать просто на других настройках оптимизации. Поэтому надо прям изучать по хорошим руководствам.
     
    Последнее редактирование: 23 дек 2022
    mantissa нравится это.
  18. ormoulu

    ormoulu Well-Known Member

    Публикаций:
    0
    Регистрация:
    24 янв 2011
    Сообщения:
    1.206
    Оптимизировать такие вещи в 22-23 году ассемблерными вставками занятие бессмысленное, а часто ещё и глупое, в случае компилятора Интел или даже Майкрософт вы просто утомитесь соревноваться с компилем в оптимизации.
    Вставки имеет смысл делать в случае каких-либо архитектурно зависимых вещей типа математических операций или векторов прерываний. Ну или если у вас чип в ошейнике для муравья, где каждый бит дорог, или шелл на двести байт, и то часто си сделает лучше.
     
  19. Intro

    Intro Active Member

    Публикаций:
    0
    Регистрация:
    29 авг 2009
    Сообщения:
    607
    gcc ещё плохо контролирует регистры, и может быть так, что код вообще не будет работать, у меня не работал, использовал инструкцию счётчика тактов для контроля быстродействия, VS справилась, а gcc нет, правда, я потом код подправил. Вот поэтому из х64 VS ассемблер удалили. Так что лучше асм код в длл, и использовать UASM или другие асмы с плюшками. И айтити синтаксис не перевариваю.
     
  20. aa_dav

    aa_dav Active Member

    Публикаций:
    0
    Регистрация:
    24 дек 2008
    Сообщения:
    479
    GCC вообще не анализирует никак текст инлайн-асма - для него это чёрный ящик. Поэтому надо через параметры-аннотации директивы инлайн-асма явно обозначать какие регистры трогаются, что является входными данными а что выходными.
    https://gamedev.ru/code/articles/gcc_inline_asm
    Например так:
    Код (Text):
    1.  
    2.     uint32_t a,b=5;
    3.     asm(
    4.         "movl %[arg_b],%%eax\n\t"
    5.         "incl %%eax\n\t"
    6.         :"=a"(a)
    7.         :[arg_b]"m"(b)
    8.         :"cc");
    9.  
    Здесь мы первой аннотацией поясняем GCC, что после выполнения нашего асма содержимое eax ассоциируется с переменной a, т.е. если вдруг сразу же после выполнения куска нашего асма нужна переменная a - её актуальное значение лежит в регистре eax и чтобы оно было актуальным в дальнейшем компилятору надо в памяти её значение обновить из eax. Он уже сделает это сам по своим соображениям оптимизации как ему лучше.
    Следующей аннотацией тут говорится, что мы рассчитываем, что нам на входе нужна переменная b и что мы возьмём её из памяти - а значит если компилятор до этого куска кода её закешировал в регистре - надо выложить обратно в память, чтобы не поломать логику нашего асма. Он это учтёт и сделает всё в лучшем виде.
    И последней аннотацией (clobber list) мы поясняем, что загрязняем регистр флагов - компилятор не может рассчитывать, что тот не изменился после выполнения этого асма.
    Прикол в том, что мы можем сказать, что на входе у нас в регистре eax должна лежать одна переменная (компилятор обязан это обеспечить), в регистре ebx - другая и так далее - и где эти переменные лежат на выходе.
    Поэтому компилятор сам на входе сделает нужные mov для заполнения исходных данных, а на выходе рассуёт их через mov в выходные переменные.
    Это позволяет GCC гибко оптимизировать, например, сишные inline-функции - полностью выкидывать перекладывания в память и обратно аргументов если функция чьё тело описано на асме заинлайнится прямо в месте вызова. Что MS VC сделать не может - он по природе встроенного в синтаксис асма вынужден будет повторять инструкции по загрузке переменных из памяти в регистры и обратно - просто сам принцип таков, что у него в теле функции должны быть инструкции по загрузке регистров из аргументов, а значит они будут заинлайнены тоже даже если совершенно не нужны. Поэтому в GCC можно сделать inline-функцию на асме над аргументами которая на выходе в лучшем случае превратится в одну инструкцию кода в бинарнике, а на MS VC это принципиально невозможно.
     
    Последнее редактирование: 23 дек 2022
    dcc0 нравится это.