Помимо всех других проблем, я не думаю, что кто-то еще упоминал, что код в его окончательном виде в памяти не может быть вообще перемещен.Ваш пример foo
функция, может быть, но рассмотрим:
int main(int argc, char **argv) {
if (argc == 3) {
return 1;
} else {
return 0;
}
}
Часть результата:
if (argc == 3) {
401149: 83 3b 03 cmpl $0x3,(%ebx)
40114c: 75 09 jne 401157 <_main+0x27>
return 1;
40114e: c7 45 f4 01 00 00 00 movl $0x1,-0xc(%ebp)
401155: eb 07 jmp 40115e <_main+0x2e>
} else {
return 0;
401157: c7 45 f4 00 00 00 00 movl $0x0,-0xc(%ebp)
40115e: 8b 45 f4 mov -0xc(%ebp),%eax
}
Обратите внимание на jne 401157 <_main+0x27>
.В этом случае у нас есть условная инструкция перехода x86 0x75 0x09
, которая идет на 9 байт вперед.Так что это можно перемещать: если мы копируем код в другое место, мы все равно хотим идти вперед на 9 байтов.Но что, если это был относительный переход или вызов кода, который не является частью функции, которую вы скопировали?Вы бы перепрыгнули в произвольное место в вашем стеке или рядом с ним.
Не все инструкции перехода и вызова одинаковы (не для всех архитектур и даже не для всех в x86).Некоторые ссылаются на абсолютные адреса, загружая адрес в регистр, а затем совершая дальний переход / вызов.Когда код подготовлен к выполнению, так называемый «загрузчик» «исправит» код, заполнив любой адрес, который в конечном итоге получит целевой объект в памяти.Копирование такого кода приведет (в лучшем случае) к тому, что код переходит на тот же адрес или вызывает тот же адрес, что и оригинал.Если цель отсутствует в коде, который вы копируете, это, вероятно, то, что вы хотите.Если цель находится в коде, который вы копируете, то вы переходите к оригиналу, а не к копии.
Те же проблемы относительных и абсолютных адресов применимы к вещам, отличным от кода.Например, ссылки на разделы данных (содержащие строковые литералы, глобальные переменные и т. Д.) Будут неправильными, если они адресованы относительно и не являются частью скопированного кода.
Кроме того, указатель на функцию не выполняетобязательно содержать адрес первой инструкции в функции.Например, на процессоре ARM в режиме взаимодействия ARM / большого пальца адрес функции большого пальца на 1 больше, чем адрес ее первой инструкции.По сути, младший значащий бит значения не является частью адреса, это флаг, указывающий процессору переключаться в режим большого пальца как часть перехода.