У меня есть исходный код C ++, который я анализирую с помощью clang, создавая байт-код llvm. С этого момента я хочу обработать файл сам ...
Однако я допустил проблему. Рассмотрим следующий сценарий:
- Я создаю класс с нетривиальным деструктором или конструктором копирования.
- Я определяю функцию, в которой объект этого класса передается в качестве параметра по значению (без ссылки или указателя).
В полученном байт-коде вместо этого я получаю указатель. Для классов без деструктора параметр аннотируется как «byval», но в данном случае это не так.
В результате я не могу различить, передается ли параметр по значению или действительно по указателю.
Рассмотрим следующий пример:
Входной файл - cpass.cpp:
class C {
public:
int x;
~C() {}
};
void set(C val, int x) {val.x=x;};
void set(C *ptr, int x) {ptr->x=x;}
Командная строка компиляции:
clang++ -c cpass.cpp -emit-llvm -o cpass.bc; llvm-dis cpass.bc
Произведенный выходной файл (cpass.ll):
; ModuleID = 'cpass.bc'
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64"
target triple = "x86_64-unknown-linux-gnu"
%class.C = type { i32 }
define void @_Z3set1Ci(%class.C* %val, i32 %x) nounwind {
%1 = alloca i32, align 4
store i32 %x, i32* %1, align 4
%2 = load i32* %1, align 4
%3 = getelementptr inbounds %class.C* %val, i32 0, i32 0
store i32 %2, i32* %3, align 4
ret void
}
define void @_Z3setP1Ci(%class.C* %ptr, i32 %x) nounwind {
%1 = alloca %class.C*, align 8
%2 = alloca i32, align 4
store %class.C* %ptr, %class.C** %1, align 8
store i32 %x, i32* %2, align 4
%3 = load i32* %2, align 4
%4 = load %class.C** %1, align 8
%5 = getelementptr inbounds %class.C* %4, i32 0, i32 0
store i32 %3, i32* %5, align 4
ret void
}
Как видите, параметры обеих функций set
выглядят одинаково. Так как я могу сказать, что первая функция должна была принимать параметр по значению вместо указателя?
Одним из решений может быть как-то разобрать искаженное имя функции, но оно не всегда может быть жизнеспособным. Что если кто-то поставит extern "C"
перед функцией?
Есть ли способ указать clang
оставить аннотацию byval
или создать дополнительную аннотацию для каждого параметра функции, переданного значением?
Антон Коробейников предлагает мне разобраться с ИК-излучением LLVM от Clang. К сожалению, я почти ничего не знаю о внутренностях clang, документация довольно скудная. Руководство по внутреннему оборудованию от Clang не говорит об инфракрасном излучении. Так что я не знаю, с чего начать, куда идти, чтобы решить проблему, надеюсь, без фактического прохождения всего исходного кода Clang. Есть указатели? Советы? Дальнейшее чтение?
В ответе Антона Коробейникова:
Я более или менее знаю, как выглядит C ++ ABI в отношении передачи параметров. Нашел хорошее чтение здесь: http://agner.org./optimize/calling_conventions.pdf. Но это очень зависит от платформы! Этот подход может оказаться невозможным на разных архитектурах или в некоторых особых обстоятельствах.
В моем случае, например, функция будет работать на другом устройстве, а не на том, откуда она вызывается. Два устройства не разделяют память, поэтому они даже не делят стек. Если пользователь не передает указатель (в этом случае мы предполагаем, что он знает, что он делает), объект всегда должен передаваться в сообщении с параметрами функции. Если он имеет нетривиальный конструктор копирования, он должен быть выполнен вызывающей стороной, но объект также должен быть создан в области параметров.
Итак, я хотел бы как-то переопределить ABI в clang, не слишком вмешиваясь в их исходный код. Или, может быть, добавьте некоторую дополнительную аннотацию, которая будет игнорироваться в обычном конвейере компиляции, но я мог обнаружить это при разборе файла .bc / .ll. Или как-то иначе реконструировать сигнатуру функции.