2010-04-15 4 views
5

У меня есть нечетный самомодифицирующийся код, но в корне его довольно простая проблема: я хочу иметь возможность выполнить jmp (или call), а затем из что произвольная точка генерирует исключение и попадает в блок try/catch, содержащий jmp/call.Выбрасывание исключения C++ после перехода inline-asm

Но когда я это делаю (в gcc 4.4.1 x86_64), исключение приводит к terminate(), как если бы исключение было выбрано из-за пределов попытки/catch. Я действительно не вижу, как это отличается от того, что выбрасывает исключение из какой-либо обширной библиотеки, но очевидно, что это просто не работает.

Как я могу выполнить jmp или call, но все же исключить исключение для первоначального try/catch? Почему этот try/catch не продолжает обрабатывать эти исключения, как если бы функция вызывалась нормально?

Код:

#include <iostream> 
#include <stdexcept> 

using namespace std; 

void thrower() 
{ 
    cout << "Inside thrower" << endl; 
    throw runtime_error("some exception"); 
} 

int main() 
{ 
    cout << "Top of main" << endl; 

    try { 
     asm volatile (
      "jmp *%0" // same thing happens with a call instead of a jmp 
      : 
      : "r"((long)thrower) 
      : 
     ); 
    } catch (exception &e) { 
     cout << "Caught : " << e.what() << endl; 
    } 
    cout << "Bottom of main" << endl << endl; 
} 

Ожидаемый результат:

Top of main 
Inside thrower 
Caught : some exception 
Bottom of main 

Фактический выход:

Top of main 
Inside thrower 
terminate called after throwing an instance of 'std::runtime_error' 
    what(): some exception 
Aborted 
+0

Первоначально я задал аналогичный вопрос в контексте обработчиков сигналов в Linux. Это действительно просто омрачало проблему, поэтому я удалил ее и попросил эту более упрощенную версию. Вся вещь обработчика сигналов просто маскировала мою реальную проблему. – SoapBox

+1

Существует примерно два разных способа реализации обработки исключений. Один из них заключается в том, чтобы украсить код и вызвать сайты, чтобы распространять контекст повсюду, а другой - индексировать точки вызова и возврата, чтобы обработчик исключений мог искать, куда идти. В любом случае, inline asm чего-то не хватает. – spraff

ответ

1

Если вы используете GCC 4.4.7 (и выше) на x86-64 Linux с карликовой механизмом исключения ручки (который может быть по умолчанию один), у меня есть способ решить эту проблему ,

Предположим, что ваш встроенный код сборки является функцией inline_add. Он вызовет другую функцию add, которая может вызвать исключение. Вот код:

extern "C" int add(int a, int b) { 
    throw "in add"; 
} 

int inline_add(int a, int b) { 
    int r = 0; 
    __asm__ __volatile__ (
     "movl %1, %%edi\n\t" 
     "movl %2, %%esi\n\t" 
     "call add\n\t" 
     "movl %%eax, %0\n\t" 
     :"=r"(r) 
     :"r"(a), "r"(b) 
     :"%eax" 
    ); 
    return r; 
} 

Если вы звоните inline_add так:

try { 
    inline_add(1, 1); 
} catch (...) { 
    std::cout << "in catch" << std::endl; 
} 

это будет крах, потому что НКУ не содержит рамку исключения для inline_add. Когда дело доходит до исключения, он должен расколоться. (См here для «Совместимости с C»)

Таким образом, мы должны подделать кадр исключения для него, но было бы трудно взломать с сборкой GCC, мы просто использовать функции с телосложением исключения окружить его

мы определим функцию следующим образом:

void build_exception_frame(bool b) { 
    if (b) { 
     throw 0; 
    } 
} 

и называют inline_add так:

try { 
    inline_add(1, 1); 
    build_exception_frame(false); 
} catch (...) { 
    std::cout << "in catch" << std::endl; 
} 

и это только горе RKS.

build_exception_frame должен прийти после вызова, или он не будет работать

Более того, чтобы предотвратить optimiziong GCC может взять на себя build_exception_frame, нам нужно добавить следующее:

void build_exception_frame(bool b) __attribute__((optimize("O0"))); 

Вы можете проверить код сборки, который gcc генерирует для проверки кода.

Кажется, что gcc обеспечивает кадр исключения для всего try/catch, если есть одна функция, которая может бросить, и положение имеет значение.

Необходимо посмотреть, как работает gcc позже.

Если кто знает об этом, будьте любезны, чтобы сообщить мне. Благодарю.

1

Вы смотрели на ассемблере, порожденного НКУ, если ваша попытка {} блок просто содержит вызов функции? Я почти уверен, что компилятор C++ делает больше, чем просто прыжок в этой точке, поскольку он должен иметь возможность отслеживать трассировку стека при возникновении исключения.

Ваш код может быть работоспособен, если вы смогли имитировать шаги, которые gcc берет при структурировании вызова функции.

Update:this question может предоставить более подробную информацию. В частности, может быть использована метательная и захватывающая часть Itanium ABI: link

+0

Этот вопрос не дает гораздо больше информации. Моя слабая попытка понять обработку исключений (в Mac OS X) привела меня к некоторому нечетному коду 'libunwind', для которого я не мог найти открытый исходный код. Он был написан на C++ (!) И имел соглашения об именах Apple. Точка, это не строго функциональность GCC. Посмотрите в источнике GCC, и вы увидите, что это зависит от ОС, поставляемого libunwind. – Potatoswatter

+0

Нет, похоже, что asm - это просто вызов функции (я добавляю комментарии inline-asm вокруг вызова, и между комментариями нет комментариев, кроме самого вызова). – SoapBox

+0

@SoapBox: вам нужно сравнить таблицы диспетчеризации исключений, чтобы увидеть, какие функции, которые * * вставлены в 'try {' и 'throw', работают. – Potatoswatter

3

Вы изучили, как ваша реализация обрабатывает исключения? Это связано с поиском адресов ПК в таблицах, чтобы выяснить, что программа делает то место, которое бросало, и то, что делали все вызывающие. По крайней мере, на Mac OS X GCC.

Единственная другая система, на которую я смотрел, Metrowerks Codewarrior для Mac (обратный путь назад) использовал подобную систему, хотя и несколько более прозрачную. Компилятор может прозрачно вставлять функции для любого изменения контекста исключения.

У вас нет молитвы сделать этот переносной компьютер.

+0

Интересно ... Но если я внедряю законный рабочий вызов «thrower()» внутри одного и того же try/catch, версия inline-asm все еще не работает, и это звучит так, как будто вы говорите.Я думаю, обработчик исключений мог бы смотреть на адрес самого вызова, о котором он не знает. Это самый многообещающий ответ до сих пор .... – SoapBox

1

Как только вы выполните встроенную сборку, у вас есть реализация, определенная поведением (7.4/1).

Вы должны попытаться установить раму стека; детали являются специфичными для платформы, и я не знаю, как это сделать на x86_64.

Смежные вопросы