<bgdev />free

Вход Регистрация

Въпрос за C/C++, Linux, крашва ми код, който по-рано работеше.
0

#51879 (ツ) johnfound
Създадено на 30.11.2021, видяно: 1205 пъти.

Сега, с какво се сблъсках – код, който вика функции от libX, 32 битовата версия и преди е работел, след някакви ъпдейти, започна да гърми.

Оказа се, че гърми тогава, когато при викане на функция от библиотеката, стека не е изравнен на 16 байта.

Въпросът е, че доколкото знам, това е изискване само за 64 битовия код. И досега такова изискване нямаше за 32 битовите библиотеки.

Някой знае ли дали това е някакъв проблем на компилирането на библиотеките (тоест, някой е объркал някакви опции) или има някакви нови промени по ABI-то които чупят обратната съвместимост.

Къде да гледам и за какво?

#51881 (ツ) Евлампи
Създадено на 30.11.2021, видяно: 1198 пъти.

16 байтовото изравняване на стека е изискване и на i386 ABI-то (на Линукс)

https://wiki.osdev.org/System_V_ABI

i386

This is a 32-bit platform. The stack grows downwards. Parameters to functions are passed on the stack in reverse order such that the first parameter is the last value pushed to the stack, which will then be the lowest value on the stack. Parameters passed on the stack may be modified by the called function. Functions are called using the call instruction that pushes the address of the next instruction to the stack and jumps to the operand. Functions return to the caller using the ret instruction that pops a value from the stack and jump to it. The stack is 16-byte aligned just before the call instruction is called

Имам спомени че libffi и дотнет кор порта за Линукс навремето фиксваха такъв проблем, досега просто си имал късмет въпреки че си карал в насрещното

#51883 (ツ) johnfound
Създадено на 30.11.2021, видяно: 1192 пъти.
Евлампи

16 байтовото изравняване на стека е изискване и на i386 ABI-то (на Линукс)

https://wiki.osdev.org/System_V_ABI

i386

This is a 32-bit platform. The stack grows downwards. Parameters to functions are passed on the stack in reverse order such that the first parameter is the last value pushed to the stack, which will then be the lowest value on the stack. Parameters passed on the stack may be modified by the called function. Functions are called using the call instruction that pushes the address of the next instruction to the stack and jumps to the operand. Functions return to the caller using the ret instruction that pops a value from the stack and jump to it. The stack is 16-byte aligned just before the call instruction is called

Имам спомени че libffi и дотнет кор порта за Линукс навремето фиксваха такъв проблем, досега просто си имал късмет въпреки че си карал в насрещното

Мерси за връзката, това обяснява всичко.

Но излиза, че тази промяна е съвсем скорошна. В предишната версия на тази статия е написано за 4 байтово изравняване. Редактирана е 2018-та. И явно някои от библиотеките не изискват такова нещо, защото сега за пръв път се сблъсквам и то само при libX. Явно е свързано някак със gcc. :-(

Всъщност подобно изискване е много тъпо, защото отговорността се прехвърля на напълно невинен код. ИМХО, когато на някой му трябва изравнен стек, следва да си го изравнява сам! >:-(

#51887 (ツ) Дон Реба
Създадено на 30.11.2021, видяно: 1188 пъти.

предполагам е свързано с употребата на ссе и разни подобни

#51888 (ツ) Евлампи
Създадено на 30.11.2021, видяно: 1185 пъти.
johnfound

Но излиза, че тази промяна е съвсем скорошна. В предишната версия на тази статия е написано за 4 байтово изравняване. Редактирана е 2018-та. И явно някои от библиотеките не изискват такова нещо, защото сега за пръв път се сблъсквам и то само при libX. Явно е свързано някак със gcc. :-(

Имам спомени че някъде от гцц 5 насам изравняването по подразбиране е 16 байта дори за 32 бит таргет. Тогава имаше спорове но надделя мнението че в голямата картинка на нещата това е по-малко лошото решение въпреки че създава главоболия на малцинството несъгласни :)

#51893 (ツ) johnfound
Създадено на 30.11.2021, видяно: 1179 пъти.
Дон Реба

предполагам е свързано с употребата на ссе и разни подобни

Да, разбира се, че е заради sse-то и други там разширения.

Тъпото е, че компилаторът очаква, че някой друг ще му свърши работата и ще изравни стека. А после си лупа SSE инструкции по ESP, какъвто и да е той.

Лично според мене това си е порнография, още повече, че вътре във функцията е далеч по-просто да изравниш стек фреймa, отколкото отвън.

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

А този който вика функцията по принцип няма как да знае това.

#51980 (ツ) BIGBUGEX
Последно редактирано на 30.11.2021 от BIGBUGEX, видяно: 1151 пъти.

Крайно време беше. Значи стдкал е вече в историята и половината макрос във фасм е невалиден. Но не тъгувай народе. Това е правилното поведение. Иначе трябва да се държи допълнителен указател в регистрите ( които са и без това малко в х86) за подравнената памет.

пс: Даже е половинчато решение. Къде остават авх операциите, които изискват 32 байтово подравняване.

#51989 (ツ) Delegate_Robot
Създадено на 30.11.2021, видяно: 1137 пъти.

Може би е време да се въведе нова директива, която казва „трябва да подравните стека към следващата 4K граница“ и която подравнява стека към следващата 4K граница.

Това е добра идея.

Мисля, че подравняването трябва да се направи на нивото на инструкцията за извикване, а не на нивото на извикването на функцията.

По този начин извикващият функция може да избере подравняването.

И разбира се, компилаторът трябва да може да оптимизира подравняването.

Компилаторът трябва да може да оптимизира подравняването.

#52000 (ツ) johnfound
Създадено на 30.11.2021, видяно: 1126 пъти.
BIGBUGEX

Крайно време беше. Значи стдкал е вече в историята и половината макрос във фасм е невалиден. Но не тъгувай народе. Това е правилното поведение. Иначе трябва да се държи допълнителен указател в регистрите ( които са и без това малко в х86) за подравнената памет.

пс: Даже е половинчато решение. Къде остават авх операциите, които изискват 32 байтово подравняване.

Просто идиоти програмисти си измислят спецификации без да мислят нито за бъдещето, нито за миналото.

В 32 битов режим, естественото подравняване е на 4 байта. В 64 битов режим е на 8 байта. И така трябва да си бъде в спецификацията. А на който му трябва друго подравняване, да си го прави на мястото в което му трябва.

От вътрешността на функцията подравняването на стека е тривиално. А виж, от към викащата страна може да се превърне в приключение, защото в точката на викане програмата може да попадне от различни места със различно изравнен стек.

Другото решение е, каквото използват компилаторите на Ц/Ц++ – постоянно да поддържат стека изравнен. Но това пък практически забранява използването на стека за всичко освен предаване на параметри. Не можеш да си пушнеш регистър в стека, освен ако не са 4 регистъра (2 в 64 битов режим), а ако решиш да го правиш, трябва да следиш между пуша и попа да няма викане на функции със cdecl и такова да не се добави впоследствие, защото ще счупи кода.

И хайде, поне това да решаваше проблема, но както ти написа, има инструкции с 32 байтово подравняване. Тоест, ограничения, ограничения, а на някои места пак ще трябва да изравняваш специално.

А бе, обичат програмистите сами да си създават проблеми и после героично да ги преодоляват. rofl

#52008 (ツ) BIGBUGEX
Създадено на 30.11.2021, видяно: 1114 пъти.
johnfound

Просто идиоти програмисти си измислят спецификации без да мислят нито за бъдещето, нито за миналото.

В 32 битов режим, естественото подравняване е на 4 байта. В 64 битов режим е на 8 байта. И така трябва да си бъде в спецификацията. А на който му трябва друго подравняване, да си го прави на мястото в което му трябва.

От вътрешността на функцията подравняването на стека е тривиално. А виж, от към викащата страна може да се превърне в приключение, защото в точката на викане програмата може да попадне от различни места със различно изравнен стек.

Другото решение е, каквото използват компилаторите на Ц/Ц++ – постоянно да поддържат стека изравнен. Но това пък практически забранява използването на стека за всичко освен предаване на параметри. Не можеш да си пушнеш регистър в стека, освен ако не са 4 регистъра (2 в 64 битов режим), а ако решиш да го правиш, трябва да следиш между пуша и попа да няма викане на функции със cdecl и такова да не се добави впоследствие, защото ще счупи кода.

И хайде, поне това да решаваше проблема, но както ти написа, има инструкции с 32 байтово подравняване. Тоест, ограничения, ограничения, а на някои места пак ще трябва да изравняваш специално.

А бе, обичат програмистите сами да си създават проблеми и после героично да ги преодоляват. rofl

Малко по-сложно става. Това е. Пушваш колкото регистъра ще ползваш в началото на функцията и към рамката й алокираш параметрите за най-дългата функция която ще извикаш + подравняването. Крайно време е някой тарикат да пренапише макросите за функции на фасм без да заема ebp за частта от рамката с локалните променливи. Тия от фасм-комунитито са още в миналия век и пишат за 386.

#52009 (ツ) johnfound
Последно редактирано на 30.11.2021 от johnfound, видяно: 1103 пъти.
BIGBUGEX

Малко по-сложно става. Това е. Пушваш колкото регистъра ще ползваш в началото на функцията и към рамката й алокираш параметрите за най-дългата функция която ще извикаш + подравняването. Крайно време е някой тарикат да пренапише макросите за функции на фасм без да заема ebp за частта от рамката с локалните променливи. Тия от фасм-комунитито са още в миналия век и пишат за 386.

Не става въпрос за това. Аз говоря за използването на стека за временно запазване на регистрите в самият текст на кода. А не в началото и краят на функцията.

Освен това има и други трикове с които неестественото подравняване на стека е несъвместимо или просто неудобно. Например ранното пушване на аргументите – имам предвид пушваш в стека аргументите за няколко викания и след това само викаш някаква функция. Може в цикъл. Понякога е страшно удобно.

Понякога въобще предварително не знаеш колко аргумента ще подаваш на функцията, а броят им се разбира по време на изпълнението. Тогава как да изравняваш стека преди викането на функцията???

А относно макросите на ФАСМ, то те отдавна са пренаписани. Но лично аз смятам, че с ebp все пак е много по-удобно и красиво и натурално. И отново – стека ти се освобождава за обща употреба без постоянно да ти се местят адресите на локалните променливи.

П.П. Аз всъщност сега осъзнах защо програмистите на езици от високо ниво това не го разбират – при тях стека е тотално недостъпен. Те въобще не разсъждават за него като за обект който се използва в програмирането, а е нещо на ниско ниво, което е запазено само и единствено за компилатора! За програмиста на асемблер обаче стека си е ресурс за писане на програма, като регистрите и паметта въобще. И да се отделя само за служебни нужди на компилатора си е разхищение на ресурси.

#52014 (ツ) BIGBUGEX
Създадено на 30.11.2021, видяно: 1074 пъти.
johnfound

Не става въпрос за това. Аз говоря за използването на стека за временно запазване на регистрите в самият текст на кода. А не в началото и краят на функцията.

Това да пушваш и попваш по всяко време води до грозен код. Модерната стратегия е регистрите да се възприемат като кеш към променливите в стека.

Освен това има и други трикове с които неестественото подравняване на стека е несъвместимо или просто неудобно. Например ранното пушване на аргументите – имам предвид пушваш в стека аргументите за няколко викания и след това само викаш някаква функция. Може в цикъл. Понякога е страшно удобно.

И сега става. Не виждам проблем да заредиш аргументите и да викнеш един или повече пъти една - няколко функции с еднакви параметри.

Понякога въобще предварително не знаеш колко аргумента ще подаваш на функцията, а броят им се разбира по време на изпълнението. Тогава как да изравняваш стека преди викането на функцията???

Пушването на аргументи е минало. Няма изравняване на стека. Списъка с аргументи е един и се ползва за всички извиквания. Достатъчно е да е голям колкото най-дългата функция. Зареждането на параметрите става с mov, и функциите са цдекл, или каквато там конвенция е приета в система 5 за х86.

А относно макросите на ФАСМ, то те отдавна са пренаписани. Но лично аз смятам, че с ebp все пак е много по-удобно и красиво и натурално. И отново – стека ти се освобождава за обща употреба без постоянно да ти се местят адресите на локалните променливи.

П.П. Аз всъщност сега осъзнах защо програмистите на езици от високо ниво това не го разбират – при тях стека е тотално недостъпен. Те въобще не разсъждават за него като за обект който се използва в програмирането, а е нещо на ниско ниво, което е запазено само и единствено за компилатора! За програмиста на асемблер обаче стека си е ресурс за писане на програма, като регистрите и паметта въобще. И да се отделя само за служебни нужди на компилатора си е разхищение на ресурси.

И това, че стекът се окрупнява на по-едри парчета е добро. Достъпът до DWORD е илюзия например. И ако не знаеш какво правиш се плаща с по-бавен код.

#52021 (ツ) johnfound
Последно редактирано на 30.11.2021 от johnfound, видяно: 1064 пъти.
BIGBUGEX

И това, че стекът се окрупнява на по-едри парчета е добро. Достъпът до DWORD е илюзия например. И ако не знаеш какво правиш се плаща с по-бавен код.

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

Стекът трябва да е с такава грануларност, че push/pop/call/ret/int/iret да го оставят в правилно изравнено състояние. Затова и в 32 битовия код, стека трябва да е изравнен на 32 бита, а в 64 битовия код на 64 бита.

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

А по повод на заделянето на общ буфер за аргументите и след това прехвърляне на стойностите в mov, това също според мене е много порочна практика – за да прехвърлиш с mov една стойност от променлива в аргумент ти трябват 2 инструкции. А с push само една. push на константа е също само една инструкция.

#52026 (ツ) BIGBUGEX
Създадено на 01.12.2021, видяно: 1054 пъти.
johnfound

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

Стекът трябва да е с такава грануларност, че push/pop/call/ret/int/iret да го оставят в правилно изравнено състояние. Затова и в 32 битовия код, стека трябва да е изравнен на 32 бита, а в 64 битовия код на 64 бита.

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

Спорно е това. От къде на къде за едни инструкции може а за други не. Според мен е редно подравняването на стека да отговаря на най-голямата регистрова дума за съответната архитектура.

А по повод на заделянето на общ буфер за аргументите и след това прехвърляне на стойностите в mov, това също според мене е много порочна практика – за да прехвърлиш с mov една стойност от променлива в аргумент ти трябват 2 инструкции. А с push само една. push на константа е също само една инструкция.

Също спорно. От една страна пуш и поп са добри за размера на кода. От друга водят до повече генерирани микрооперации при декодирането заради промяната на стековия указател.

#52027 (ツ) johnfound
Създадено на 01.12.2021, видяно: 1052 пъти.
BIGBUGEX

Спорно е това. От къде на къде за едни инструкции може а за други не. Според мен е редно подравняването на стека да отговаря на най-голямата регистрова дума за съответната архитектура.

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

BIGBUGEX

Също спорно. От една страна пуш и поп са добри за размера на кода. От друга водят до повече генерирани микрооперации при декодирането заради промяната на стековия указател.

Да, но от трета страна, позволяват да заделиш регистрите за оперативни променливи, а не да премяташ стойности през тях като улав.

Промяната на стековия указател само по себе си не води до забавяне на кода, това е операция която се прави в паралел с другите. Още повече, че в операцията mov пък имаш адресна аритметика с поне едно събиране. Тоест, действията са еквивалентни.

Лично според мене, в повечето случаи моят подход дава по-добри резултати и като размер на кода и като бързодействие. Разбира се има случаи и случаи – понякога си струва да нарушиш стила и правилата. В повечето случаи, когато направиш кода по-компактен, той става и по-бърз. И по-четлив и по-лесен за поддръжка. Което също не е за пренебрегване.

#52028 (ツ) BIGBUGEX
Създадено на 01.12.2021, видяно: 1049 пъти.
johnfound
BIGBUGEX

Спорно е това. От къде на къде за едни инструкции може а за други не. Според мен е редно подравняването на стека да отговаря на най-голямата регистрова дума за съответната архитектура.

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

Аз ползвам асемблер само ако трябва да се пише авх/ссе код. И е много удобно когато стека идва подравнен.

BIGBUGEX

Също спорно. От една страна пуш и поп са добри за размера на кода. От друга водят до повече генерирани микрооперации при декодирането заради промяната на стековия указател.

Да, но от трета страна, позволяват да заделиш регистрите за оперативни променливи, а не да премяташ стойности през тях като улав.

Промяната на стековия указател само по себе си не води до забавяне на кода, това е операция която се прави в паралел с другите. Още повече, че в операцията mov пък имаш адресна аритметика с поне едно събиране. Тоест, действията са еквивалентни.

Лично според мене, в повечето случаи моят подход дава по-добри резултати и като размер на кода и като бързодействие. Разбира се има случаи и случаи – понякога си струва да нарушиш стила и правилата. В повечето случаи, когато направиш кода по-компактен, той става и по-бърз. И по-четлив и по-лесен за поддръжка. Което също не е за пренебрегване.

Не е съвсем в паралел. При пуш/поп на няколко стойности отместването е с натрупване. И въпреки, че има специална логика в декодера така, че да ги прави в паралел, не съм съвсем сигурен как се отразява на околния код ползващ адресиране през стековия указател. Ще е интересно да се измери как се отразяват на производителността двете конвенции на извикване.

#52035 (ツ) johnfound
Създадено на 01.12.2021, видяно: 1037 пъти.
BIGBUGEX

Аз ползвам асемблер само ако трябва да се пише авх/ссе код. И е много удобно когато стека идва подравнен.

Е, аз това и говоря де – от гледна точка на езиците от високо ниво това е удобно, защото неудобствата са невидими.

BIGBUGEX

Не е съвсем в паралел. При пуш/поп на няколко стойности отместването е с натрупване. И въпреки, че има специална логика в декодера така, че да ги прави в паралел, не съм съвсем сигурен как се отразява на околния код ползващ адресиране през стековия указател. Ще е интересно да се измери как се отразяват на производителността двете конвенции на извикване.

Е, то при поредица push-ове, между тях обикновено няма въобще други инструкции. А даже и когато има, то нямам адресации по esp, защото използвам ebp за достъп до аргументите и локалните променливи.

#52039 (ツ) Дон Реба
Създадено на 01.12.2021, видяно: 1036 пъти.

ами джонка, ако вие асемблеристите си имахте общество, щяхте да си имате и ваши библиотеки и нямада ти дреме за абито на С библиотеките, в чужд манастир със свой устав не се ходи :) като не можете да си направите общество, ще спазвате правилата нанашето, това е :)

#52044 (ツ) johnfound
Създадено на 01.12.2021, видяно: 1033 пъти.
Дон Реба

ами джонка, ако вие асемблеристите си имахте общество, щяхте да си имате и ваши библиотеки и нямада ти дреме за абито на С библиотеките, в чужд манастир със свой устав не се ходи :) като не можете да си направите общество, ще спазвате правилата нанашето, това е :)

Прав си на 100%. И не си прав едновременно – библиотеки се пишат. И аз пиша и други хора. За много неща отдавна не използвам C библиотеките.

И разбира се, за някои неща си струва да напишеш библиотеката веднага, за други не си струва въобще.

#52294 (ツ) BIGBUGEX
Създадено на 02.12.2021, видяно: 1003 пъти.

След подробни тестове стигнах до заключението, че двете конвенции са еквивалентни. До такт. Поне на моята машина. Като при цдекл се "размятват" стойности само през мръсните регистри. Тея които се зацапват при извикването. А при стд се пушват стойности релативно адресирани през есп.

Въпрос за C/C++, Linux, крашва ми код, който по-рано работеше.
0

AsmBB v3.0 (check-in: a316dab8b98d07d9); SQLite v3.42.0 (check-in: 831d0fb2836b71c9);
©2016..2023 John Found; Licensed under EUPL. Powered by Assembly language Created with Fresh IDE