встроенная проблема c++ at&t asm

Мой код

const int howmany = 5046;
char buffer[howmany];
    asm("lea     buffer,%esi"); //Get the address of buffer
    asm("mov     howmany,%ebx");         //Set the loop number
    asm("buf_loop:");                      //Lable for beginning of loop
    asm("movb     (%esi),%al");             //Copy buffer[x] to al
    asm("inc     %esi");                   //Increment buffer address
    asm("dec     %ebx");                   //Decrement loop count
    asm("jnz     buf_loop");              //jump to buf_loop if(ebx>0)

Моя проблема

Я использую компилятор gcc. По какой-то причине мои переменные буфера/сколько не определены в глазах моего asm. Я не уверен, почему. Я просто хочу переместить начальный адрес моего буферного массива в регистр esi, зациклить его «сколько» раз, копируя каждый элемент в регистр al.


person Kelly Elton    schedule 24.12.2009    source источник
comment
@paxdiablo - не компилируется. Нет никакого поведения.   -  person paxdiablo    schedule 24.12.2009
comment
@ kelton52, тогда проблема во время компиляции, а не во время выполнения. Там все еще должно быть поведение, возможно, сообщения об ошибках от компилятора?   -  person Kelly Elton    schedule 24.12.2009
comment
@paxdiablo - Ваши комментарии не очень продуктивны. Тому, кто знает, что делает, моя ошибка должна быть очевидна.   -  person paxdiablo    schedule 24.12.2009
comment
@ kelton52: Невозможно узнать, что кто-то здесь делает, пока вы хотя бы не скажете людям, какой компилятор вы используете. Нет (и не может быть) стандарта и/или согласованности между компиляторами, когда речь идет о встроенном ассемблере. До тех пор здесь ничего не может быть очевидным.   -  person Kelly Elton    schedule 24.12.2009
comment
@ kelton52 Я видел несколько просителей с худшим отношением.   -  person AnT    schedule 24.12.2009
comment
@ kelton52, как я упоминал в своем ответе: если вы используете gcc, ваша ошибка очевидна - вы не используете %0 и %1, а используете выражения C напрямую в своем ассемблерном коде, и это не не работает в gcc. Если вы используете какой-либо другой компилятор, укажите, КАКОЙ из них, и мы посмотрим, есть ли рядом специалисты по этому конкретному диалекту, которые могут вам помочь (в противном случае ПОПРОБУЙТЕ решение, которое я рекомендую для gcc в любом случае: оно вполне может работать в других компиляторы тоже!-).   -  person phoebus    schedule 24.12.2009
comment
Ха-ха, молодец, @Kelton52. Просить о помощи, а затем оскорблять тех, кто ее предлагает, на самом деле не способствует решению вашей проблемы :-) Люди, которые задают вопросы так, как будто у меня есть проблема, не делают себе никаких одолжений - я просто указал, что хорошо в вопросах будет указано, что вы ожидаете и что происходит на самом деле - мы не умеем читать мысли (и вы все еще не опубликовали фактическое сообщение об ошибке). У меня мало времени, чтобы тратить его на неблагодарных, когда здесь есть более благодарные люди. В любом случае, удачи в решении вашей проблемы, хорошего настроения и хороших рождественских каникул.   -  person Alex Martelli    schedule 24.12.2009
comment
@kelton52: Это неочевидно, потому что мы не всеведущие и не экстрасенсы. Технические проблемы должны быть четко определены, прежде чем их можно будет решить. Что касается ожидаемого/фактического поведения, то не компиляция — это поведение, но довольно широкое. Любой вывод компилятора, даже если не создаются файлы и не печатаются сообщения, считается частью его поведения. catb.org/~esr/faqs/smart-questions.html   -  person paxdiablo    schedule 24.12.2009
comment
У меня не было плохого отношения. Кто-то, кто работал с этим раньше, должен иметь возможность посмотреть на мой код и сказать: «О, вы не можете этого сделать» или «вам нужно изменить это», как Алекс ниже. Это моя вина, что я не упомянул gcc сразу, и это очень важно. И ничто из того, что я сказал, не гарантировало, что вы все «кусаете» в ответ, как вы это сделали. Я благодарен за полезную информацию, такую ​​​​как «эй, дип ****, ты забыл поставить компилятор, который ты используешь», но поведение и коды ошибок, это очевидная вещь ... коды прокомментированы. Если кто-то работал с gcc и встроенным ассемблером, как Алекс ниже, он знает, что это неправильно.   -  person outis    schedule 24.12.2009
comment
@Alex & AndreyT: Спасибо.   -  person Kelly Elton    schedule 24.12.2009
comment
@kelton52: тебя не кусали, пока ты не оскорбил кого-то, кто знает, что делает... комментарий (читай catb.org/~esr/faqs/smart-questions.html#keepcool для образа мышления, против которого вы выступаете). Чтобы участвовать в сообществе, вы должны соблюдать его стандарты. В частности, вопросы должны быть четко сформулированы, а просьбы о разъяснении и дополнительной информации всегда должны выполняться. Помните, мы хотим помочь, но не можем без достаточной информации. Кроме того, могут быть проблемы, о которых вы не знаете, поэтому часто требуется дополнительная информация.   -  person Kelly Elton    schedule 24.12.2009
comment
Я ничего не понимаю в этой ерунде, связанной с watcom... Я довольно много читал и до сих пор не могу понять... Я думаю, что должен иметь возможность просто переместить адрес своего буфера в регистр.   -  person outis    schedule 25.12.2009


Ответы (3)


Вы используете встроенный ассемблер в gcc? (Если нет, то в каком другом компиляторе С++?)

Если gcc, см. подробности здесь и в особенно этот пример:

    asm ("leal (%1,%1,4), %0"
         : "=r" (five_times_x)
         : "r" (x) 
         );

%0 и %1 относятся к переменным C-уровня, и они перечислены специально как второй (для выходных данных) и третий (для входных параметров) параметры для asm. В вашем примере у вас есть только «входы», поэтому у вас будет пустой второй операнд (традиционно после этого двоеточия используется комментарий, например /* no output registers */, чтобы указать это более явно).

person Alex Martelli    schedule 24.12.2009
comment
Какой ватком? Я говорю о gcc -- в ЭТОМ компиляторе (который, знаете ли, очень распространен) вы должны использовать %0, %1 и т. д. в самом ассемблерном коде, а затем подключать их к входным и выходным выражениям C во втором и третий аргумент _1_ -- вот и все. Не стесняйтесь не одобрять дизайн gcc (и вносить в него изменения, поскольку он с открытым исходным кодом), но я рассказываю вам, как он ДЕЙСТВИТЕЛЬНО работает. Код вашего примера не соответствует этим правилам, поэтому он не работает (если ваш компилятор - gcc ;-). - person Kelly Elton; 24.12.2009
comment
Это не близко к тому, чтобы решить, насколько неверен код OP. Нет очевидного способа перейти от этого ответа к безопасной встроенной ассемблерной версии цикла OP. Вероятно, мне не стоило тратить час на написание ответа на этот вопрос восьмилетней давности, но я все равно это сделал ›.‹ - person Alex Martelli; 24.12.2009
comment
Я забыл добавить const в свой пример, так что это не проблема. Все равно спасибо. - person Peter Cordes; 17.11.2017

Часть, которая объявляет такой массив

int howmany = 5046;
char buffer[howmany];

недействителен С++. В С++ невозможно объявить массив, который имеет «переменный» размер или размер времени выполнения. В объявлениях массивов C++ размер всегда является константой времени компиляции.

Если ваш компилятор разрешает это объявление массива, это означает, что он реализует его как расширение. В этом случае вам нужно провести собственное исследование, чтобы выяснить, как он реализует такой массив размера во время выполнения внутри. Я предполагаю, что внутри buffer будет реализовано как указатель, а не как истинный массив. Если моя догадка верна и это действительно указатель, то правильным способом загрузки адреса массива в esi может быть

mov buffer,%esi

а не lea, как в вашем коде. lea будет работать только с "нормальными" массивами размера времени компиляции, но не с массивами размера времени выполнения.

Другой вопрос заключается в том, действительно ли вам нужен массив размера времени выполнения в вашем коде. Может быть, вы просто сделали это по ошибке? Если вы просто измените объявление howmany на

const int howmany = 5046;

массив превратится в «обычный» массив C++, и ваш код может начать работать как есть (т.е. с lea).

person AnT    schedule 24.12.2009
comment
Диалект GNU C++ поддерживает VLA в стиле C99 в качестве расширения. Любой компилятор, который поддерживает встроенный ассемблер в стиле GNU, также будет поддерживать это. (Если это не должно быть в глобальной области видимости... о, но это не компилируется, потому что нет символа _1_, так что да, это локальный VLA.) - person Kelly Elton; 24.12.2009
comment
@Peter Cordes: Да, но я сомневаюсь, что GNU сможет понять, что в случае VLA buffer нужно заменить на _2_. С точки зрения ассемблера, и _3_, и _4_ имеют свой собственный смысл. - person Peter Cordes; 17.11.2017
comment
Ни один из них не подходит для local, потому что они не отображаются как имена символов в выводе asm. Вот почему вам нужно использовать ограничение ввода. В этом отношении VLA ничем не отличаются от обычных массивов с автоматическим хранением. - person AnT; 17.11.2017
comment
Опубликовал свой собственный ответ, потому что ни ваш, ни принятый ответ не приближаются к решению того, что не так с кодом OP. (А ваш точно неправильный, он либо не соберется, либо подгрузит первые 4 байта, если массив статический/глобальный) - person Peter Cordes; 17.11.2017
comment
Чтобы оставаться последовательным и упростить обслуживание, не было бы предпочтительнее использовать _1_ вместо _2_ - person Peter Cordes; 17.11.2017

Все эти asm-инструкции должны находиться в одной и той же инструкции asm, если вы хотите быть уверенными, что они непрерывны (без сгенерированного компилятором кода между ними), и вам нужно объявить ввод/вывод/затирание операнды или вы наступите на регистры компилятора.

Вы не можете использовать lea или mov в/из имени переменной C (за исключением глобальных/статических символов, которые фактически определены в выводе компилятора asm, но даже в этом случае обычно не следует).

Вместо использования инструкций mov для настройки входных данных попросите компилятор сделать это за вас, используя ограничения входных операндов. Если это первая или последняя инструкция встроенного ассемблерного оператора GNU C, обычно это означает, что вы делаете это неправильно и пишете неэффективный код.

И кстати, GNU C++ допускает массивы переменной длины в стиле C99, поэтому howmany может быть не-const и даже устанавливаться таким образом, чтобы не оптимизировать его до константы. Любой компилятор, который может скомпилировать встроенный ассемблер в стиле GNU, также будет поддерживать массивы переменной длины.


Совет при возникновении проблемы: (1) Каково ожидаемое поведение? (2) Каково наблюдаемое поведение?

Если это выглядит слишком сложным, тогда https://gcc.gnu.org/wiki/DontUseInlineAsm. Напишите автономную функцию на ассемблере, чтобы вы могли просто изучить ассемблер вместо того, чтобы изучать gcc и его сложный, но мощный встроенный ассемблерный интерфейс. Вы в основном должны знать asm и понимать компиляторы, чтобы использовать его правильно (с правильными ограничениями, чтобы предотвратить поломку, когда включена оптимизация).

Обратите внимание на использование именованных операндов, таких как %[ptr] вместо %2 или %%ebx. Позволить компилятору выбирать, какие регистры использовать, обычно хорошо, но для x86 есть буквы, отличные от "r", которые вы можете использовать, например, "=a" для rax/eax/ax/al, в частности. См. https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html, а также другие ссылки в вики тега inline-assembly.

Я также использовал buf_loop%=: для добавления уникального номера к метке, поэтому, если оптимизатор клонирует функцию или встраивает ее в несколько мест, файл все равно будет собираться.

Я использовал %%al в качестве примера явного написания имен регистров: расширенный ассемблер (с операндами) нуждается в двойном %, чтобы получить литерал % в выводе ассемблера. Вы также можете использовать %[res] или %0 и позволить компилятору заменить %al в выводе asm. (И тогда у вас не было бы причин использовать ограничение конкретного регистра, если только вы не хотите воспользоваться преимуществом cbw или lodsb или чего-то подобного.) result равно unsigned char, поэтому компилятор выберет для него байтовый регистр. Если вам нужен младший байт более широкого операнда, вы можете использовать, например, %b[count].

void ext(char *);

int foo(void) 
{
    int howmany = 5046;   // could be a function arg
    char buffer[howmany];
    //ext(buffer);

    const char *bufptr = buffer;  // copy the pointer to a C var we can use as a read-write operand
    unsigned char result;
    asm("buf_loop%=:  \n\t"                 // do {
        "   movb     (%[ptr]), %%al \n\t"   // Copy buffer[x] to al
        "   inc     %[ptr]        \n\t"
        "   dec     %[count]      \n\t"
        "   jnz     buf_loop      \n\t"      // } while(ebx>0)
       :   [res]"=a"(result)      // al = write-only output
         , [count] "+r" (howmany) // input/output operand, any register
         , [ptr] "+r" (bufptr)
       : // no input-only operands
       : "memory"   // we read memory that isn't an input operand, only pointed to by inputs
    );
    return result;
}

При этом используется "memory" стиратель, что неэффективно. Вам не нужно, чтобы компилятор перебрасывал все в память, только чтобы убедиться, что содержимое buffer[] в памяти соответствует состоянию абстрактной машины C. (Это не гарантируется передачей указателя на него в регистре).

gcc7.2 -O3 вывод:

Без затирания памяти или ограничения ввода leave появляется перед встроенным ассемблерным блоком, освобождая память стека до того, как встроенный ассемблер использует устаревший указатель. Обработчик сигнала, запущенный в неподходящее время, забьет его.

    pushq   %rbp
    movl    $5046, %edx
    movq    %rsp, %rbp
    subq    $5056, %rsp
    movq    %rsp, %rcx         # compiler-emitted to satisfy our "+r" constraint for bufptr
    # start of the inline-asm block
    buf_loop18:  
       movb     (%rcx), %al 
       inc     %rcx        
       dec     %edx      
       jnz     buf_loop      
    # end of the inline-asm block

    movzbl  %al, %eax
    leave
    ret

Более эффективным способом является использование фиктивного операнда памяти, который сообщает компилятору, что весь массив является памятью, доступной только для чтения, для ввода оператора asm. См. получить длину строки во встроенном ассемблере GNU, чтобы узнать больше об этом трюке с гибкими элементами массива для сообщения компилятору вы читаете весь массив без явного указания длины.


В C вы можете определить новый тип внутри приведения, но вы не можете в C++, поэтому using вместо действительно сложного входного операнда.

Я также использовал ограничение соответствия, поэтому buffer мог быть входом напрямую, а выходной операнд в том же регистре означает, что мы можем изменить этот регистр. Мы получили тот же эффект в foo(), используя const char *bufptr = buffer;, а затем используя ограничение чтения-записи, чтобы сообщить компилятору, что новое значение этой переменной C — это то, что мы оставляем в регистре. В любом случае мы оставляем значение в мертвой переменной C, которая выходит за пределы области видимости без чтения, но способ ограничения сопоставления может быть полезен для макросов, где вы не хотите изменять значение вашего ввода (и не нуждаетесь в тип вашего ввода: int dummy тоже подойдет.)

int bar(unsigned howmany)
{
    //int howmany = 5046;
    char buffer[howmany];
    //ext(buffer);
    buffer[0] = 1;
    buffer[100] = 100;   // test whether we got the input constraints right

    //using input_t = const struct {char a[howmany];};  // requires a constant size
    using flexarray_t = const struct {char a; char x[];};
    const char *dummy;
    unsigned char result;
    asm("buf_loop%=:  \n\t"                 // do {
        "   movb     (%[ptr]), %%al \n\t"   // Copy buffer[x] to al
        "   inc     %[ptr]        \n\t"
        "   dec     %[count]      \n\t"
        "   jnz     buf_loop      \n\t"      // } while(ebx>0)
       : [res]"=a"(result)        // al = write-only output
         , [count] "+r" (howmany) // input/output operand, any register
         , "=r" (dummy)           // output operand in the same register as buffer input, so we can modify the register
       : [ptr] "2" (buffer)     // matching constraint for the dummy output
         , "m" (*(flexarray_t *) buffer)  // whole buffer as an input operand

           //, "m" (*buffer)        // just the first element: doesn't stop the buffer[100]=100 store from sinking past the inline asm, even if you used asm volatile
       : // no clobbers
    );
    buffer[100] = 101;
    return result;
}

Присваивания buffer[100] = 100; и buffer[100] = 101; нужны для того, чтобы показать, что они оба появляются в ассемблерном коде, а не сливаются во встроенном ассемблере (что происходит, если вы пропустите входной операнд "m"). IDK, почему buffer[100] = 101; не оптимизирован; он мертв, так и должно быть. Также обратите внимание, что asm volatile не блокирует такое переупорядочение, так что это не альтернатива "memory" клоберу или использованию правильных ограничений.

Как правильно написать цикл

person Peter Cordes    schedule 17.11.2017
comment
@MichaelPetch: о, да, я оставил %b[res] в качестве примера использования явных имен регистров в расширенном ассемблере (где вам нужен двойной %%al). Но я забыл сказать, почему я это делаю. _3_ — это _4_, поэтому я мог просто использовать _5_ без модификатора, чтобы расширить до _6_. - person Michael Petch; 18.11.2017
comment
Да, нет необходимости в модификаторе %%al (я не обратил внимания, что ваша переменная имеет ту же ширину), так как вы определили res как переменную шириной в байт. - person Peter Cordes; 18.11.2017
comment
@MichaelPetch: спасибо за отзыв, обновил ответ абзацем, который забыл написать ранее. :) - person Michael Petch; 18.11.2017
comment
Поскольку мы делали весь _1_ (или эквивалент для операндов памяти), мне стало любопытно, действительно ли это является нарушением строгих правил псевдонимов. Я заметил в недавнем прошлом (до 7.x), что GCC/G++ будет предупреждать при компиляции с _2_ (или эквивалентным). В версии 7+ нет. Я не уверен, что это сделано по замыслу или это случай, который должен предупреждать, но больше не предупреждает. Да, все элементы являются символами, поэтому можно было бы подумать, что у него не будет проблем с псевдонимом, но когда я прочитал стандарт, он кажется (мне) двусмысленным в этом вопросе. - person Peter Cordes; 18.11.2017
comment
@MichaelPetch: На самом деле, когда я писал это, у меня была такая же мысль, что это явно переход к другому типу. В руководстве gcc показано приведение к типу структуры для ввода в память фиксированного размера. Я думаю, это означает, что использование официально поддерживается, потому что документировано = поддерживается. Я думаю, что гибкие структуры массивов достаточно похожи... - person Michael Petch; 18.11.2017
comment
Я знаю, что во встроенных документах по сборке есть примеры, касающиеся приведения, но я не припомню ни одного, специально использующего структуру с гибким членом. Хотя, возможно, я пропустил это. Обычно приведения включают массивы с одинаковыми типами элементов. Я, кажется, припоминаю идею с такой структурой в списке рассылки, но я сомневаюсь, что это задокументировано. - person Peter Cordes; 18.11.2017
comment
В этом случае я думаю, что _1_ выглядит безопаснее, поскольку структура не используется, и мы используем _2_ для определения размера рассматриваемого буфера. - person Michael Petch; 18.11.2017
comment
@MichaelPetch: это не работает для VLA; Типы C/C++ должны иметь постоянные размеры времени компиляции. Хотя с "m" (*(const char (*)[howmany]) buffer) все в порядке. re: документы, я имел в виду фиксированный размер, а не гибкий. Но в последних документах теперь есть массив неопределенного размера: howmany в качестве входных данных для _3_ - person Michael Petch; 18.11.2017
comment
Во встроенных документах есть пример, который не использует VLA, но передает массив с количеством элементов. Например, это есть в документации const howmany = 5046;, где "m" (*(const char (*)[]) p) не является значением времени компиляции. - person Peter Cordes; 18.11.2017
comment
Пример, о котором я думал, все еще в руководстве gcc6.4 "m" (*(const double (*)[n]) x). Этот беспорядок выглядит как оператор-выражение GNU C. - person Michael Petch; 18.11.2017
comment
@MichaelPetch: Вы правы, я зациклился на том, чтобы поместить его в {"m"( ({ struct { char x[10]; } *p = (void *)ptr ; *p; }) )}, и это та часть, которая не работает. Указатель на массив вместо _2_ синтаксически необычен, но кажется идеальным для этого. - person Peter Cordes; 18.11.2017
comment
(Обновление идеи фиктивного операнда памяти: Как я могу указать, что память, на которую *указывает* встроенный аргумент ASM, может использоваться? является каноническим Q&A для этого.) - person Peter Cordes; 18.11.2017
comment
Source + compiler asm output on the Godbolt compiler explorer. - person Peter Cordes; 04.06.2021