Я хочу проанализировать ассемблерный код, вызывающий функции, и для каждого «вызова» выяснить, сколько аргументов передается функции. Я предполагаю, что мне недоступны целевые функции, а только вызывающий код. Я ограничиваюсь кодом, который был скомпилирован только с помощью GCC, и соглашением о вызовах ABI в System V. Я попытался выполнить сканирование из каждой инструкции «вызова», но мне не удалось найти достаточно хорошего соглашения (например, где остановить сканирование? Что происходит при двух последующих вызовах с теми же аргументами?). Помощь очень ценится.
Сколько аргументов передается при вызове функции?
Ответы (1)
Репост моих комментариев в качестве ответа.
Вы не можете достоверно сказать об этом в оптимизированном коде. И даже для хорошей работы большую часть времени, вероятно, требуется ИИ человеческого уровня. например оставила ли функция значение в RSI, потому что это второй аргумент, или она просто использовала RSI в качестве временного регистра при вычислении значения для RDI (первый аргумент)? Как говорит Росс, код, сгенерированный gcc для соглашений о вызовах stack-args, имеет более очевидные шаблоны, но по-прежнему нелегко обнаружить.
Также потенциально сложно отличить хранилища, которые передают локальные переменные в стек, и хранилища, которые хранят аргументы в стеке (поскольку gcc может и иногда использует mov
хранилища для аргументов стека: см. -maccumulate-outgoing-args
). Один из способов определить разницу состоит в том, что локальные переменные будут перезагружены позже, но всегда предполагается, что аргументы затираются.
что происходит при двух последующих вызовах с одними и теми же аргументами?
Компиляторы всегда перезаписывают аргументы перед тем, как сделать следующий вызов, потому что они предполагают, что функции сбивают их аргументы (даже в стеке). ABI говорит, что функции «владеют» своими аргументами. Компиляторы действительно создают код, который делает это (см. Комментарии), но код, сгенерированный компилятором, не всегда готов перенаправить память стека, содержащую свои аргументы, для хранения совершенно разных аргументов, чтобы включить оптимизацию хвостового вызова. :( Это ненормально, потому что я точно не помню, что я видел в отношении упущенных возможностей оптимизации хвостового вызова.
Тем не менее, если аргументы передаются стеком, то, вероятно, это будет более простой случай (и я прихожу к выводу, что все 6 регистров также используются).
Даже это ненадежно. System V x86-64 ABI не просто.
int foo(int, big_struct, int)
передаст два целочисленных аргумента в регистрах, но передаст большую структуру по значению в стеке. Аргументы FP также являются серьезной проблемой. Вы не можете сделать вывод, что видение материала в стеке означает, что используются все 6 слотов для передачи целочисленных аргументов.
Windows x64 ABI значительно отличается: например, если второй аргумент (после добавления скрытого указателя возвращаемого значения, если необходимо) является целым числом / указателем, он всегда идет в RDX, независимо от того, был ли первый аргумент в RCX, XMM0, или в стеке. Это также требует, чтобы вызывающий абонент покинул «теневое пространство».
Таким образом, вы могли бы придумать некоторые эвристики, которые будут нормально работать с неоптимизированным кодом. Даже это будет сложно сделать правильно.
Что касается оптимизированного кода, сгенерированного разными компиляторами, я думаю, что было бы труднее реализовать что-то, даже близкое к полезному, чем вы когда-либо могли бы сэкономить, имея это.
void foo(int arg) { arg = 0;}
без оптимизации это сделает. При оптимизации требуется, чтобы компилятор возвращал выделенный для него регистр в стек. Например: godbolt.org/g/ogRl6n.
- person Ross Ridge; 28.12.2016
push
работает медленно. Я не вижу здесь повторного использования слотов arg. godbolt.org/g/nmRGnV
- person Peter Cordes; 28.12.2016