Ассемблер в Linux для программистов C: различия между версиями

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