Исследуя код, сгенерированный Visual Studio компилятор C++, часть 1 [дубликат]

Kevin,

Это работает без JavaScript или CSS в большинстве браузеров:

<form>
<p><input type="text" name="field1" /></p>
<p><a href="previous.html">
<button type="button">Previous Page</button></a>
<button type="submit">Next Page</button></p>
</form>

Firefox, Opera, Safari, Google Chrome вся работа.
Как всегда, IE является проблемой.

Эта версия работает, когда JavaScript включен:

<form>
<p><input type="text" name="field1" /></p>
<p><a href="previous.html">
<button type="button" onclick="window.location='previous.html'">Previous Page</button></a>
<button type="submit">Next Page</button></p>
</form>

, Таким образом, дефект в этом решении:
Предыдущая страница не работает при использовании IE с JavaScript прочь.
Мышление Вы, кнопка "Назад" все еще работает!

7
задан Community 23 May 2017 в 10:27
поделиться

4 ответа

Все, что

00401010  |> 99             /CDQ
00401011  |. 2BC2           |SUB EAX,EDX
00401013  |. D1F8           |SAR EAX,1

означает y / = 2 . Видите ли, автономный SAR не будет выполнять целочисленное деление со знаком, как предполагали авторы компилятора. Стандарт C ++ 98 рекомендует , чтобы целочисленное деление со знаком округляло результат до 0, тогда как только SAR округлял бы до отрицательной бесконечности. (Допускается округление в сторону отрицательной бесконечности, выбор остается на усмотрение реализации). Чтобы реализовать округление до 0 для отрицательных операндов, используется вышеуказанный трюк. Если вы используете беззнаковый тип вместо подписанного, то компилятор сгенерирует только одну инструкцию сдвига, так как проблема с отрицательным делением не возникнет.

Уловка довольно проста: для отрицательного y расширение знака поместит образец 11111 ... 1 в EDX , что на самом деле равно -1 в дополнении до 2 представление. Следующая SUB фактически прибавит 1 к EAX , если исходное значение y было отрицательным. Если исходный y был положительным (или 0), EDX будет содержать 0 после расширения знака, а EAX останется неизменным.

Другими словами, когда вы пишете y / = 2 со знаком y , компилятор генерирует код, который делает что-то вроде следующего

y = (y < 0 ? y + 1 : y) >> 1;

или, лучше,

y = (y + (y < 0)) >> 1;

] Обратите внимание, что стандарт C ++ не требует округления результата деления до нуля, так что компилятор имеет право сделать только одну смену даже для подписанных типов. Однако обычно компиляторы следуют рекомендации для округления до нуля (или предлагают вариант для управления поведением).

PS Я не знаю наверняка, какова цель этого LEA инструкция есть. Это действительно запретная игра. Однако я подозреваю, что это может быть просто инструкция-заполнитель, вставленная в код для дальнейшего исправления. Если я правильно помню, у компилятора MS есть опция, которая заставляет вставлять инструкции-заполнители в начале и в конце каждой функции. В будущем эта инструкция может быть перезаписана патчером с помощью инструкции CALL или JMP , которая будет выполнять код исправления. Этот конкретный LEA был выбран только потому, что он производит команду-заполнитель правильной длины без операции. Конечно, это могло быть что-то совсем другое.

11
ответ дан 6 December 2019 в 10:01
поделиться

На самом деле это не ответ на вопрос, но полезный совет. Вместо того, чтобы возиться с OllyDbg.exe, вы можете заставить Visual Studio сгенерировать для вас asm-файл, который имеет дополнительный бонус, который он может поместить в исходный исходный код в качестве комментариев. Это не имеет большого значения для вашего текущего небольшого проекта, но по мере роста вашего проекта вы можете потратить немало времени на выяснение того, какой код сборки соответствует какому исходному коду.

Из командной строки вы хотите, чтобы Параметры / FAs и / Fa ( MSDN ).

Вот часть вывода для вашего примера кода (я скомпилировал код отладки, поэтому .asm длиннее, но вы можете сделать то же самое для своего оптимизированный код):

_wmain  PROC                        ; COMDAT

; 8    : {

    push    ebp
    mov ebp, esp
    sub esp, 216                ; 000000d8H
    push    ebx
    push    esi
    push    edi
    lea edi, DWORD PTR [ebp-216]
    mov ecx, 54                 ; 00000036H
    mov eax, -858993460             ; ccccccccH
    rep stosd

; 9    :     int x=5; int y=1024;

    mov DWORD PTR _x$[ebp], 5
    mov DWORD PTR _y$[ebp], 1024        ; 00000400H
$LN2@wmain:

; 10   :     while(x) { x--; y/=2; }

    cmp DWORD PTR _x$[ebp], 0
    je  SHORT $LN1@wmain
    mov eax, DWORD PTR _x$[ebp]
    sub eax, 1
    mov DWORD PTR _x$[ebp], eax
    mov eax, DWORD PTR _y$[ebp]
    cdq
    sub eax, edx
    sar eax, 1
    mov DWORD PTR _y$[ebp], eax
    jmp SHORT $LN2@wmain
$LN1@wmain:

; 11   :     return x+y;

    mov eax, DWORD PTR _x$[ebp]
    add eax, DWORD PTR _y$[ebp]

; 12   : }

    pop edi
    pop esi
    pop ebx
    mov esp, ebp
    pop ebp
    ret 0
_wmain  ENDP

Надеюсь, это поможет!

1
ответ дан 6 December 2019 в 10:01
поделиться

lea ebx, [ebx] - это просто операция NOP. Его цель - выровнять начало цикла в памяти, что сделает его быстрее. Как вы можете видеть здесь, начало цикла начинается с адреса 0x00401010, который делится на 16. Благодаря этой инструкции

Операции CDQ и SUB EAX, EDX убедитесь, что при делении отрицательное число округляется до нуля, иначе SAR округлит его в меньшую сторону, что даст неверные результаты для отрицательных чисел.

5
ответ дан 6 December 2019 в 10:01
поделиться

Причина, по которой компилятор выдает это:

LEA EBX,DWORD PTR DS:[EBX]

вместо семантически эквивалентного:

NOP
NOP
NOP
NOP
NOP
NOP

.., заключается в том, что процессор быстрее выполняет одну 6-байтовую инструкцию, чем шесть 1- байтовые инструкции. Вот и все.

2
ответ дан 6 December 2019 в 10:01
поделиться
Другие вопросы по тегам:

Похожие вопросы: