Ассемблер в Linux для программистов C: различия между версиями
Содержимое удалено Содержимое добавлено
DannyS712 (обсуждение | вклад) м <source> -> <syntaxhighlight> (phab:T237267) |
|||
Строка 213:
Вспомним, как вы писали <code>Hello, world!</code> на Си. Скорее всего, приблизительно так:
<
#include <stdio.h>
#include <stdlib.h>
Строка 222:
exit(0);
}
</syntaxhighlight>
Вот только <code>printf(3)</code> — функция стандартной библиотеки Си, а не операционной системы. «Чем это плохо?» — спросите вы. Да, в общем, всё нормально, но, читая этот учебник, вы, вероятно, хотите узнать, что происходит «за кулисами» функций стандартной библиотеки на уровне взаимодействия с операционной системой. Это, конечно же, не значит, что из ассемблера нельзя вызывать функции библиотеки Си. Просто мы пойдём более низкоуровневым путём.
Строка 246:
А вот и сама программа:
<
#include <unistd.h>
Строка 255:
_exit(0);
}
</syntaxhighlight>
Почему <code>sizeof(str) - 1</code>? Потому, что строка в Си заканчивается нулевым байтом, а его нам печатать не нужно.
Строка 863:
Давайте подумаем, каким будет результат выполнения следующего кода на Си:
<
char x, y;
x = 250;
Строка 869:
x = x + y;
printf("%d", (int) x);
</syntaxhighlight>
Большинство сразу скажет, что результат (250 + 14 = 264) больше, чем может поместиться в одном байте. И что же напечатает программа? 8. Давайте рассмотрим, что происходит при сложении в двоичной системе.
Строка 976:
На Си это выглядело бы так:
<
#include <stdio.h>
Строка 991:
return 0;
}
</syntaxhighlight>
=== Команды сравнения и условные переходы. Безусловный переход ===
Строка 1050:
Сравните с кодом на Си:
<
if(eax == 15)
{
Строка 1057:
}
/* а сюда управление перейдёт в любом случае */
</syntaxhighlight>
Кроме команд условного перехода, область применения которых ясна сразу, также существует команда безусловного перехода. Эта команда чем-то похожа на оператор <code>goto</code> языка Си. Синтаксис:
Строка 1121:
Этот код соответствует приблизительно следующему на Си:
<
#include<stdio.h>
int main()
Строка 1143:
}
</syntaxhighlight>
Возможно, такой способ обхода массива не очень привычен для вас. В Си принято использовать переменную с номером текущего элемента, а не указатель на него. Никто не запрещает пойти этим же путём и на ассемблере:
Строка 1220:
Кто-то скажет: а ещё есть цикл <code>for()</code>! Но цикл
<
for(init; cond; incr)
{
body;
}
</syntaxhighlight>
эквивалентен такой конструкции:
<
init;
while(cond)
Строка 1236:
incr;
}
</syntaxhighlight>
И так-же:
<
for(init; decr; )
{
body;
}
</syntaxhighlight>
Эквивалетен
<pre>
Строка 1526:
Эти сложные циклические сдвиги вам редко понадобятся в реальной работе, но уже сейчас нужно знать, что такие инструкции существуют, чтобы не изобретать велосипед потом. Ведь в языке Си циклический сдвиг производится приблизительно так:
<
int main()
{
Строка 1537:
return 0;
}
</syntaxhighlight>
=== Подпрограммы ===
Строка 2143:
Далее, ищу в выводе препроцессора определение <code>dev_t</code>, нахожу:
<
typedef __dev_t dev_t;
</syntaxhighlight>
Ищу <code>__dev_t</code>:
<
__extension__ typedef __u_quad_t __dev_t;
</syntaxhighlight>
Ищу <code>__u_quad_t</code>:
<
__extension__ typedef unsigned long long int __u_quad_t;
</syntaxhighlight>
Значит, <code>sizeof(dev_t)</code> = 8.
Строка 2163:
Мы бы могли и дальше продолжать искать, но в реальности всё немного по-другому. Если вы посмотрите на определение <code>struct stat</code> (<code>cpp /usr/include/sys/stat.h | less</code>), вы увидите поля с именами <code>__pad1</code>, <code>__pad2</code>, <code>__unused4</code> и другие (зависит от системы). Эти поля не используются, они нужны для совместимости, и поэтому в <code>man</code> они не описаны. Так что самый верный способ не ошибиться — это просто попросить компилятор Си посчитать это смещение для нас (вычитаем из адреса поля адрес структуры, получаем смещение):
<
#include <sys/types.h>
#include <sys/stat.h>
Строка 2177:
return 0;
}
</syntaxhighlight>
На моей системе программа напечатала <code>sizeof = 88, offset = 44</code>. На вашей системе это значение может отличаться по описанным причинам. Теперь у нас есть все нужные данные об этой структуре, пишем программу:
Строка 2829:
Этот код эквивалентен следующему коду на Си:
<
#include <stdio.h>
Строка 2859:
return 0;
}
</syntaxhighlight>
Смотрите: в секции <code>.rodata</code> (данные только для чтения) создаётся массив из 4 значений. Мы обращаемся к нему как к обычному массиву, индексируя его по <code>%eax</code>: <code>jump_table(,%eax,4)</code>. Но зачем перед этим стоит звёздочка? Она означает, что мы хотим перейти по адресу, содержащемуся в памяти по адресу </code>jump_table(,%eax,4)</code> (если бы её не было, мы бы перешли по этому адресу и начали исполнять массив <code>jump_table</code> как код).
Строка 3397:
Рассмотрим такой код на языке Си:
<
if(((a > 5) && (b < 10)) || (c == 0))
{
do_something();
}
</syntaxhighlight>
В принципе, булево выражение можно вычислять как обычное арифметическое, то есть в такой последовательности:
Строка 3414:
Такой способ вычисления называется полным. Можем ли мы вычислить значение этого выражения быстрее? Смотрите, если <code>c == 0</code>, то всё выражение будет иметь значение true в любом случае, независимо от <code>a</code> и <code>b</code>. А вот если <code>c != 0</code>, то приходится проверять значения <code>a</code> и <code>b</code>. Таким образом, наш код (фактически) превращается в такой:
<
if(c == 0)
{
Строка 3429:
dont_do_it:
</syntaxhighlight>
В принципе, можно пойти дальше: если <code>a <= 5</code>, нас не интересует сравнение <code>b < 10</code>: всё равно выражение равно false.
<
if(c == 0)
{
Строка 3451:
dont_do_it:
</syntaxhighlight>
Такой способ вычисления выражений называется сокращённым (от англ. short-circuit evaluation), потому что позволяет вычислить выражение, не проверяя всех входящих в него подвыражений. Можно вывести такие формальные правила:
Строка 3460:
В принципе, сокращённое вычисление булевых выражений помогает написать более быстрый (а часто и более простой) код. С другой стороны, возникают проблемы, если одно из подвыражений при вычислении вызывает побочные эффекты (англ. side effects), например вызов функции:
<
if((c == 0) || foo())
{
do_something();
}
</syntaxhighlight>
Если мы используем сокращённое вычисление и оказывается, что <code>c == 0</code>, то функция <code>foo()</code> вызвана не будет, потому что от её результата значение выражения уже не зависит. Хорошо это или плохо, зависит от конкретной ситуации, но, без сомнения, способ выполнения такого кода становится не очевидным.
|