хорошо, предположим, что эпилог (то есть код в }
строке) для function_a
и для function_b
одинаков
несмотря на то, что функции A
и B
не симметричны, мы можем предположить это, потому что они имеют одинаковую сигнатуру (без параметров, без возвращаемого значения), одинаковые соглашения о вызовах и одинаковый размер локальных переменных (4 байта - int beacon = 0x0b1c2d3
против char buffer[4];
) и с оптимизацией - обе должны быть удалены, потому что неиспользованный. но мы не должны использовать дополнительные локальные переменные в function_b
, чтобы не нарушить это предположение. самый проблемный момент здесь - то, что function_A
или function_B
будет использовать энергонезависимые регистры (и в результате сохранить его в прологе и восстановить в эпилоге) - но однако похоже здесь не место для этого.
поэтому мой следующий код основан на этом предположении - epilogueA == epilogueB
(на самом деле решение @Gassa также основано на нем.
также нужно очень четко указать, что function_a
и function_b
не должны быть встроенными. это очень важно - без этого никакое решение невозможно. поэтому я позволил себе добавить атрибут noinline к function_a
и function_b
. примечание - не изменение кода, а добавление атрибута, что автор этой задачи неявно подразумевает, но не указывает явно. не знаю, как в GCC пометить функцию как noinline, но в CL для этого используется __declspec(noinline)
.
следующий код, который я пишу для компилятора CL, где существует следующая встроенная функция
void * _AddressOfReturnAddress();
но я думаю, что GCC
также должен иметь аналог этой функции. также я использую
void* _ReturnAddress();
но на самом деле _ReturnAddress() == *(void**)_AddressOfReturnAddress()
и мы можем использовать только _AddressOfReturnAddress()
. простое использование _ReturnAddress()
делает исходный (но не двоичный) код меньше и читабельнее.
и следующий код работает как для x86, так и для x64. и этот код работает (проверено) с любой оптимизацией.
несмотря на то, что я использую 2 глобальные переменные - код является потокобезопасным - действительно мы можем вызывать main
из нескольких потоков одновременно, вызывать его несколько раз - но все будет работать правильно (только, конечно, как я говорю в начале, если epilogueA == epilogueB
)
надеюсь, комментарии в коде достаточно сами объяснили
__declspec(noinline) void function_b(void){
char buffer[4];
buffer[0] = 0;
static void *IPa, *IPb;
// save the IPa address
_InterlockedCompareExchangePointer(&IPa, _ReturnAddress(), 0);
if (_ReturnAddress() == IPa)
{
// we called from function_a
function_b();
// <-- IPb
if (_ReturnAddress() == IPa)
{
// we called from function_a, change return address for return to IPb instead IPa
*(void**)_AddressOfReturnAddress() = IPb;
return;
}
// we at stack of function_a here.
// we must be really at point IPa
// and execute fprintf(stdout, "Executed function_b\n"); + '}' (epilogueA)
// but we will execute fprintf(stdout, "Executing function_b\n"); + '}' (epilogueB)
// assume that epilogueA == epilogueB
}
else
{
// we called from function_b
IPb = _ReturnAddress();
return;
}
fprintf(stdout, "Executing function_b\n");
// epilogueB
}
__declspec(noinline) void function_a(void) {
int beacon = 0x0b1c2d3;
fprintf(stdout, "Executing function_a\n");
function_b();
// <-- IPa
fprintf(stdout, "Executed function_b\n");
// epilogueA
}
int main(void) {
function_a();
fprintf(stdout, "Finished!\n");
return 0;
}
person
RbMm
schedule
19.03.2017
fprintf(stdout, "Executed function_b\n");
? - person Arash   schedule 19.03.2017function_b
на n байтов - сколько именно байтов занимаетfprintf(stdout, "Executed function_b\n");
, но в этом случае я думаю, что невозможно вычислить это правильно, так как это будет работать как на x86, так и на x64, с любой оптимизацией и соглашением о вызовах. однако когда-то подобные задачи имеют абсолютно правильное решение - для примера . ваш вопрос также задан здесь - person RbMm   schedule 19.03.2017