Директен достъп до функционалния стек

По-рано зададох въпрос относно C функции, които приемат неопределен брой параметри напр. void foo() { /* code here */ } и който може да бъде извикан с неопределен брой аргументи от неопределен тип.

Когато попитах дали е възможно функция като void foo() { /* code here */ } да получи параметрите, с които е извикана, напр. foo(42, "random") някой каза, че:

Единственото, което можете да направите, е да използвате конвенциите за извикване и знанията за архитектурата, на която работите, и да получите параметри директно от стека. източник

Въпросът ми е:

Ако имам тази функция

void foo()
{
    // get the parameters here
};

И аз го наричам: foo("dummy1", "dummy2") възможно ли е да получите 2-та параметъра във функцията foo директно от стека?

Ако да, как? Възможно ли е да имате достъп до пълния стек? Например, ако извикам функция рекурсивно, възможно ли е да имам достъп до всяко състояние на функция по някакъв начин?

Ако не, какъв е смисълът от функциите с неопределен брой параметри? Това грешка в езика за програмиране C ли е? В кои случаи някой би искал foo("dummy1", "dummy2") да компилира и работи добре за функция, чиято заглавка е void foo()?


person Jacob Krieg    schedule 02.03.2014    source източник
comment
Ако знаете как се нарича (какви са типовете параметри), можете да направите това по машинно-зависим начин (но тогава защо имате такава функция?) Ако не знаете, не можете да знаете.   -  person n. 1.8e9-where's-my-share m.    schedule 02.03.2014
comment
Също така имайте предвид, че (в съвременния C така или иначе) void foo() {} не приема никакви аргументи. void foo(); (само декларация, не е свързана с дефиницията) от друга страна не казва какво взема foo. Така че и в двата ви примера не можете да подадете нищо на foo.   -  person Mat    schedule 02.03.2014


Отговори (3)


Много „ако“:

  1. Придържате се към една версия на компилатор.
  2. Един набор от опции на компилатора.
  3. По някакъв начин успейте да убедите вашия компилатор никога да не предава аргументи в регистрите.
  4. Убедете вашия компилатор да не третира две извиквания f(5, "foo") и f(&i, 3.14) с различни аргументи към една и съща функция като грешка. (Това беше характеристика например на ранните C компилатори на DeSmet).

Тогава записът за активиране на функция е предсказуем (т.е. гледате генерирания сбор и предполагате, че винаги ще бъде един и същ): адресът за връщане ще бъде някъде там и запазеният bp (базов указател, ако вашата архитектура има такъв) и последователността на аргументите ще бъде същата. И така, как бихте знаели какви действителни параметри са предадени? Ще трябва да ги кодирате (техния размер, отместване), вероятно в първия аргумент, нещо като това, което прави printf.

Рекурсия (т.е. да бъдеш в рекурсивно повикване няма значение) всеки екземпляр има свой запис за активиране (казах ли, че трябва да убедиш своя компилатор никога да не оптимизира опашните извиквания?), но в C, за разлика от Pascal, нямаш връзка обратно към записа за активиране на повикващия (т.е. локални променливи), тъй като няма декларации на вложени функции. Получаването на достъп до пълния стек, т.е. всички записи за активиране преди текущия екземпляр, е доста досадно, податливо на грешки и представлява интерес най-вече за авторите на зловреден код, които биха искали да манипулират адреса за връщане.

Така че това е много караница и предположения за нищо.

person user1666959    schedule 02.03.2014

Да, можете да получите достъп до предадените параметри директно чрез стека. Но не, не можете да използвате дефиниция на функция в стар стил, за да създадете функция с променлив брой и тип параметри. Следният код показва как да получите достъп до параметър чрез указател на стека. Той е напълно зависим от платформата, така че нямам представа дали ще работи на вашата машина или не, но можете да схванете идеята

long foo();

int main(void)
{
    printf( "%lu",foo(7));
}

long foo(x)
 long x;
{
    register void* sp asm("rsp");
    printf("rsp = %p rsp_ value = %lx\n",sp+8, *((long*)(sp + 8)));
    return *((long*)(sp + 8)) + 12;
}
  1. вземете указател на главата на стека (rsp регистър на моята машина)
  2. добавете отместването на подаден параметър към rsp => получавате указател към long x в стека
  3. деименирайте указателя, добавете 12 (направете каквото трябва) и върнете стойността.

Отместването е проблемът, тъй като зависи от компилатора, операционната система и кой знае какво още. За този пример просто проверих, проверих го в дебъгера, но ако е наистина важно за вас, мисля, че можете да дойдете с някакво "общо" решение за вашата машина.

person Dabo    schedule 02.03.2014
comment
@Jason Swartz: Ето оригиналния отговор, играх с различни опции, ако не дефинирате параметър в дефиницията на функцията, нищо не се предава на функцията. Когато се опитате да подадете повече параметри, тогава в дефиницията на функцията се появяват излишни параметри, които са пропуснати. - person Dabo; 02.03.2014

Ако декларирате void foo(), тогава ще получите грешка при компилиране за foo("dummy1", "dummy2").

Можете да декларирате функция, която приема неопределен брой аргументи, както следва (например):

int func(char x,...);

Както можете да видите, поне един аргумент трябва да бъде посочен. Това е така, че във функцията да имате достъп до всички аргументи, които следват последния указан аргумент.

Да предположим, че имате следното обаждане:

short y = 1000;
int sum = func(1,y,5000,"abc");

Ето как можете да приложите func и да получите достъп до всеки от неуточнените аргументи:

int func(char x,...)
{
    short y = (short)((int*)&x+1)[0]; // y = 1000
    int   z = (int  )((int*)&x+2)[0]; // z = 5000
    char* s = (char*)((int*)&x+3)[0]; // s[0...2] = "abc"
    return x+y+z+s[0];                // 1+1000+5000+'a' = 6098
}

Проблемът тук, както можете да видите, е, че типът на всеки аргумент и общият брой на аргументите са неизвестни. Така че всяко извикване на func с "неподходящ" списък от аргументи може (и вероятно ще доведе) до изключение по време на изпълнение.

Следователно, обикновено първият аргумент е низ (const char*), който показва типа на всеки от следващите аргументи, както и общия брой на аргументите. Освен това има стандартни макроси за извличане на неуказаните аргументи - va_start и va_end.

Например, ето как можете да приложите функция, подобна по поведение на printf:

void log_printf(const char* data,...)
{
    static char str[256] = {0};
    va_list args;
    va_start(args,data);
    vsnprintf(str,sizeof(str),data,args);
    va_end(args);
    fprintf(global_fp,str);
    printf(str);
}

P.S.: примерът по-горе е не безопасен за нишки и е даден тук само като пример...

person barak manos    schedule 02.03.2014
comment
Вашият пример дава прототип, OP пита за случая, когато е дадена декларация на функция. Прочетете въпроса и отговора, който той свърза stackoverflow.com/a/22074190/2549281 - person Dabo; 02.03.2014
comment
С gcc 4.8.1 не получавам грешка при извикване на foo("dummy1", "dummy2"). Знам за различни функции, но това не беше моят въпрос. Не знаех обаче за подхода, който сте използвали за int func(char x,...), знаех само за този пример: gnu.org/software/libc/manual/html_node/ - person Jacob Krieg; 02.03.2014
comment
@Dabo защо премахна отговора си? Работеше и сега го четях по-внимателно?... - person Jacob Krieg; 02.03.2014
comment
@Jason Swartz: Не съм сигурен от коментара ви дали този отговор е полезен или не. Уведомете ме, ако не, и аз ще го премахна. - person barak manos; 02.03.2014
comment
@barakmanos Бих искал да го запазиш. Както Dabo каза, съвременният C не позволява този код, но някои компилатори го компилират успешно, напр. GCC. - person Jacob Krieg; 02.03.2014
comment
@Jason Swartz: Няма проблем обаче, какво общо има това със C#? - person barak manos; 02.03.2014
comment
@JasonSwartz премахнах отговора, защото разбрах, че добавям прототип към функцията. Възстанових го, тъй като ви интересуваше, но няма да работи, ако премахнете long x; от дефиницията на функцията. - person Dabo; 02.03.2014