Въпрос за C/C++, Linux, крашва ми код, който по-рано работеше.
johnfound
Създадено на 30.11.2021, видяно: 1535 пъти. #51879
Сега, с какво се сблъсках – код, който вика функции от libX, 32 битовата версия и преди е работел, след някакви ъпдейти, започна да гърми.
Оказа се, че гърми тогава, когато при викане на функция от библиотеката, стека не е изравнен на 16 байта.
Въпросът е, че доколкото знам, това е изискване само за 64 битовия код. И досега такова изискване нямаше за 32 битовите библиотеки.
Някой знае ли дали това е някакъв проблем на компилирането на библиотеките (тоест, някой е объркал някакви опции) или има някакви нови промени по ABI-то които чупят обратната съвместимост.
Къде да гледам и за какво?
Евлампи
Създадено на 30.11.2021, видяно: 1528 пъти. #51881
16 байтовото изравняване на стека е изискване и на i386 ABI-то (на Линукс)
Имам спомени че libffi и дотнет кор порта за Линукс навремето фиксваха такъв проблем, досега просто си имал късмет въпреки че си карал в насрещното
johnfound
Създадено на 30.11.2021, видяно: 1522 пъти. #51883
Мерси за връзката, това обяснява всичко.
Но излиза, че тази промяна е съвсем скорошна. В предишната версия на тази статия е написано за 4 байтово изравняване. Редактирана е 2018-та. И явно някои от библиотеките не изискват такова нещо, защото сега за пръв път се сблъсквам и то само при libX. Явно е свързано някак със gcc.
Всъщност подобно изискване е много тъпо, защото отговорността се прехвърля на напълно невинен код. ИМХО, когато на някой му трябва изравнен стек, следва да си го изравнява сам!
ДонРеба
Създадено на 30.11.2021, видяно: 1518 пъти. #51887
предполагам е свързано с употребата на ссе и разни подобни
Евлампи
Създадено на 30.11.2021, видяно: 1515 пъти. #51888
Но излиза, че тази промяна е съвсем скорошна. В предишната версия на тази статия е написано за 4 байтово изравняване. Редактирана е 2018-та. И явно някои от библиотеките не изискват такова нещо, защото сега за пръв път се сблъсквам и то само при libX. Явно е свързано някак със gcc.
Имам спомени че някъде от гцц 5 насам изравняването по подразбиране е 16 байта дори за 32 бит таргет. Тогава имаше спорове но надделя мнението че в голямата картинка на нещата това е по-малко лошото решение въпреки че създава главоболия на малцинството несъгласни :)
johnfound
Създадено на 30.11.2021, видяно: 1509 пъти. #51893
предполагам е свързано с употребата на ссе и разни подобни
Да, разбира се, че е заради sse-то и други там разширения.
Тъпото е, че компилаторът очаква, че някой друг ще му свърши работата и ще изравни стека. А после си лупа SSE инструкции по ESP, какъвто и да е той.
Лично според мене това си е порнография, още повече, че вътре във функцията е далеч по-просто да изравниш стек фреймa, отколкото отвън.
Да не говорим, че отвътре на функцията знаеш кога ти трябва и кога не и можеш да го правиш само когато е нужно.
А този който вика функцията по принцип няма как да знае това.
Крайно време беше. Значи стдкал е вече в историята и половината макрос във фасм е невалиден. Но не тъгувай народе. Това е правилното поведение. Иначе трябва да се държи допълнителен указател в регистрите ( които са и без това малко в х86) за подравнената памет.
пс: Даже е половинчато решение. Къде остават авх операциите, които изискват 32 байтово подравняване.
Може би е време да се въведе нова директива, която казва „трябва да подравните стека към следващата 4K граница“ и която подравнява стека към следващата 4K граница.
Това е добра идея.
Мисля, че подравняването трябва да се направи на нивото на инструкцията за извикване, а не на нивото на извикването на функцията.
По този начин извикващият функция може да избере подравняването.
И разбира се, компилаторът трябва да може да оптимизира подравняването.
Компилаторът трябва да може да оптимизира подравняването.
johnfound
Създадено на 30.11.2021, видяно: 1456 пъти. #52000
Крайно време беше. Значи стдкал е вече в историята и половината макрос във фасм е невалиден. Но не тъгувай народе. Това е правилното поведение. Иначе трябва да се държи допълнителен указател в регистрите ( които са и без това малко в х86) за подравнената памет.
пс: Даже е половинчато решение. Къде остават авх операциите, които изискват 32 байтово подравняване.
Просто идиоти програмисти си измислят спецификации без да мислят нито за бъдещето, нито за миналото.
В 32 битов режим, естественото подравняване е на 4 байта. В 64 битов режим е на 8 байта. И така трябва да си бъде в спецификацията. А на който му трябва друго подравняване, да си го прави на мястото в което му трябва.
От вътрешността на функцията подравняването на стека е тривиално. А виж, от към викащата страна може да се превърне в приключение, защото в точката на викане програмата може да попадне от различни места със различно изравнен стек.
Другото решение е, каквото използват компилаторите на Ц/Ц++ – постоянно да поддържат стека изравнен. Но това пък практически забранява използването на стека за всичко освен предаване на параметри. Не можеш да си пушнеш регистър в стека, освен ако не са 4 регистъра (2 в 64 битов режим), а ако решиш да го правиш, трябва да следиш между пуша и попа да няма викане на функции със cdecl и такова да не се добави впоследствие, защото ще счупи кода.
И хайде, поне това да решаваше проблема, но както ти написа, има инструкции с 32 байтово подравняване. Тоест, ограничения, ограничения, а на някои места пак ще трябва да изравняваш специално.
А бе, обичат програмистите сами да си създават проблеми и после героично да ги преодоляват.
BIGBUGEX
Създадено на 30.11.2021, видяно: 1444 пъти. #52008
Просто идиоти програмисти си измислят спецификации без да мислят нито за бъдещето, нито за миналото.
В 32 битов режим, естественото подравняване е на 4 байта. В 64 битов режим е на 8 байта. И така трябва да си бъде в спецификацията. А на който му трябва друго подравняване, да си го прави на мястото в което му трябва.
От вътрешността на функцията подравняването на стека е тривиално. А виж, от към викащата страна може да се превърне в приключение, защото в точката на викане програмата може да попадне от различни места със различно изравнен стек.
Другото решение е, каквото използват компилаторите на Ц/Ц++ – постоянно да поддържат стека изравнен. Но това пък практически забранява използването на стека за всичко освен предаване на параметри. Не можеш да си пушнеш регистър в стека, освен ако не са 4 регистъра (2 в 64 битов режим), а ако решиш да го правиш, трябва да следиш между пуша и попа да няма викане на функции със cdecl и такова да не се добави впоследствие, защото ще счупи кода.
И хайде, поне това да решаваше проблема, но както ти написа, има инструкции с 32 байтово подравняване. Тоест, ограничения, ограничения, а на някои места пак ще трябва да изравняваш специално.
А бе, обичат програмистите сами да си създават проблеми и после героично да ги преодоляват.
Малко по-сложно става. Това е. Пушваш колкото регистъра ще ползваш в началото на функцията и към рамката й алокираш параметрите за най-дългата функция която ще извикаш + подравняването. Крайно време е някой тарикат да пренапише макросите за функции на фасм без да заема ebp за частта от рамката с локалните променливи. Тия от фасм-комунитито са още в миналия век и пишат за 386.
Малко по-сложно става. Това е. Пушваш колкото регистъра ще ползваш в началото на функцията и към рамката й алокираш параметрите за най-дългата функция която ще извикаш + подравняването. Крайно време е някой тарикат да пренапише макросите за функции на фасм без да заема ebp за частта от рамката с локалните променливи. Тия от фасм-комунитито са още в миналия век и пишат за 386.
Не става въпрос за това. Аз говоря за използването на стека за временно запазване на регистрите в самият текст на кода. А не в началото и краят на функцията.
Освен това има и други трикове с които неестественото подравняване на стека е несъвместимо или просто неудобно. Например ранното пушване на аргументите – имам предвид пушваш в стека аргументите за няколко викания и след това само викаш някаква функция. Може в цикъл. Понякога е страшно удобно.
Понякога въобще предварително не знаеш колко аргумента ще подаваш на функцията, а броят им се разбира по време на изпълнението. Тогава как да изравняваш стека преди викането на функцията???
А относно макросите на ФАСМ, то те отдавна са пренаписани. Но лично аз смятам, че с ebp все пак е много по-удобно и красиво и натурално. И отново – стека ти се освобождава за обща употреба без постоянно да ти се местят адресите на локалните променливи.
П.П. Аз всъщност сега осъзнах защо програмистите на езици от високо ниво това не го разбират – при тях стека е тотално недостъпен. Те въобще не разсъждават за него като за обект който се използва в програмирането, а е нещо на ниско ниво, което е запазено само и единствено за компилатора! За програмиста на асемблер обаче стека си е ресурс за писане на програма, като регистрите и паметта въобще. И да се отделя само за служебни нужди на компилатора си е разхищение на ресурси.
BIGBUGEX
Създадено на 30.11.2021, видяно: 1404 пъти. #52014
Не става въпрос за това. Аз говоря за използването на стека за временно запазване на регистрите в самият текст на кода. А не в началото и краят на функцията.
Това да пушваш и попваш по всяко време води до грозен код. Модерната стратегия е регистрите да се възприемат като кеш към променливите в стека.
Освен това има и други трикове с които неестественото подравняване на стека е несъвместимо или просто неудобно. Например ранното пушване на аргументите – имам предвид пушваш в стека аргументите за няколко викания и след това само викаш някаква функция. Може в цикъл. Понякога е страшно удобно.
И сега става. Не виждам проблем да заредиш аргументите и да викнеш един или повече пъти една - няколко функции с еднакви параметри.
Понякога въобще предварително не знаеш колко аргумента ще подаваш на функцията, а броят им се разбира по време на изпълнението. Тогава как да изравняваш стека преди викането на функцията???
Пушването на аргументи е минало. Няма изравняване на стека. Списъка с аргументи е един и се ползва за всички извиквания. Достатъчно е да е голям колкото най-дългата функция. Зареждането на параметрите става с mov, и функциите са цдекл, или каквато там конвенция е приета в система 5 за х86.
А относно макросите на ФАСМ, то те отдавна са пренаписани. Но лично аз смятам, че с ebp все пак е много по-удобно и красиво и натурално. И отново – стека ти се освобождава за обща употреба без постоянно да ти се местят адресите на локалните променливи.
П.П. Аз всъщност сега осъзнах защо програмистите на езици от високо ниво това не го разбират – при тях стека е тотално недостъпен. Те въобще не разсъждават за него като за обект който се използва в програмирането, а е нещо на ниско ниво, което е запазено само и единствено за компилатора! За програмиста на асемблер обаче стека си е ресурс за писане на програма, като регистрите и паметта въобще. И да се отделя само за служебни нужди на компилатора си е разхищение на ресурси.
И това, че стекът се окрупнява на по-едри парчета е добро. Достъпът до DWORD е илюзия например. И ако не знаеш какво правиш се плаща с по-бавен код.
И това, че стекът се окрупнява на по-едри парчета е добро. Достъпът до DWORD е илюзия например. И ако не знаеш какво правиш се плаща с по-бавен код.
Това, че стекът е добре да се окрупнява е вярно! Но, това не може да става заради няколко разширени инструкции и за сметка на всички останали инструкции на процесора.
Стекът трябва да е с такава грануларност, че push/pop/call/ret/int/iret да го оставят в правилно изравнено състояние. Затова и в 32 битовия код, стека трябва да е изравнен на 32 бита, а в 64 битовия код на 64 бита.
Разширенията всъщност нямат никакво отношение към стека. Те просто изискват памет с определено изравняване. Това, че функциите използват стека за алокиране на променливи не им дава правото да си налагат своите изисквания. И забележи, това въобще е възможно само и единствено заради използването на езици от високо ниво, в които стека въобще липсва като управляем обект с който може да се работи.
А по повод на заделянето на общ буфер за аргументите и след това прехвърляне на стойностите в mov, това също според мене е много порочна практика – за да прехвърлиш с mov една стойност от променлива в аргумент ти трябват 2 инструкции. А с push само една. push на константа е също само една инструкция.
BIGBUGEX
Създадено на 01.12.2021, видяно: 1384 пъти. #52026
Това, че стекът е добре да се окрупнява е вярно! Но, това не може да става заради няколко разширени инструкции и за сметка на всички останали инструкции на процесора.
Стекът трябва да е с такава грануларност, че push/pop/call/ret/int/iret да го оставят в правилно изравнено състояние. Затова и в 32 битовия код, стека трябва да е изравнен на 32 бита, а в 64 битовия код на 64 бита.
Разширенията всъщност нямат никакво отношение към стека. Те просто изискват памет с определено изравняване. Това, че функциите използват стека за алокиране на променливи не им дава правото да си налагат своите изисквания. И забележи, това въобще е възможно само и единствено заради използването на езици от високо ниво, в които стека въобще липсва като управляем обект с който може да се работи.
Спорно е това. От къде на къде за едни инструкции може а за други не. Според мен е редно подравняването на стека да отговаря на най-голямата регистрова дума за съответната архитектура.
А по повод на заделянето на общ буфер за аргументите и след това прехвърляне на стойностите в mov, това също според мене е много порочна практика – за да прехвърлиш с mov една стойност от променлива в аргумент ти трябват 2 инструкции. А с push само една. push на константа е също само една инструкция.
Също спорно. От една страна пуш и поп са добри за размера на кода. От друга водят до повече генерирани микрооперации при декодирането заради промяната на стековия указател.
johnfound
Създадено на 01.12.2021, видяно: 1382 пъти. #52027
Спорно е това. От къде на къде за едни инструкции може а за други не. Според мен е редно подравняването на стека да отговаря на най-голямата регистрова дума за съответната архитектура.
Да, ако въпросните инструкции въобще имат отношение към стека. Но те нямат. Те имат отношение към адресите в паметта. Което съгласи се, но не е същото. Стека си има инструкции, които работят с него и те са които определят необходимата структура на стека.
Също спорно. От една страна пуш и поп са добри за размера на кода. От друга водят до повече генерирани микрооперации при декодирането заради промяната на стековия указател.
Да, но от трета страна, позволяват да заделиш регистрите за оперативни променливи, а не да премяташ стойности през тях като улав.
Промяната на стековия указател само по себе си не води до забавяне на кода, това е операция която се прави в паралел с другите. Още повече, че в операцията mov пък имаш адресна аритметика с поне едно събиране. Тоест, действията са еквивалентни.
Лично според мене, в повечето случаи моят подход дава по-добри резултати и като размер на кода и като бързодействие. Разбира се има случаи и случаи – понякога си струва да нарушиш стила и правилата. В повечето случаи, когато направиш кода по-компактен, той става и по-бърз. И по-четлив и по-лесен за поддръжка. Което също не е за пренебрегване.
BIGBUGEX
Създадено на 01.12.2021, видяно: 1379 пъти. #52028
Спорно е това. От къде на къде за едни инструкции може а за други не. Според мен е редно подравняването на стека да отговаря на най-голямата регистрова дума за съответната архитектура.
Да, ако въпросните инструкции въобще имат отношение към стека. Но те нямат. Те имат отношение към адресите в паметта. Което съгласи се, но не е същото. Стека си има инструкции, които работят с него и те са които определят необходимата структура на стека.
Аз ползвам асемблер само ако трябва да се пише авх/ссе код. И е много удобно когато стека идва подравнен.
Също спорно. От една страна пуш и поп са добри за размера на кода. От друга водят до повече генерирани микрооперации при декодирането заради промяната на стековия указател.
Да, но от трета страна, позволяват да заделиш регистрите за оперативни променливи, а не да премяташ стойности през тях като улав.
Промяната на стековия указател само по себе си не води до забавяне на кода, това е операция която се прави в паралел с другите. Още повече, че в операцията mov пък имаш адресна аритметика с поне едно събиране. Тоест, действията са еквивалентни.
Лично според мене, в повечето случаи моят подход дава по-добри резултати и като размер на кода и като бързодействие. Разбира се има случаи и случаи – понякога си струва да нарушиш стила и правилата. В повечето случаи, когато направиш кода по-компактен, той става и по-бърз. И по-четлив и по-лесен за поддръжка. Което също не е за пренебрегване.
Не е съвсем в паралел. При пуш/поп на няколко стойности отместването е с натрупване. И въпреки, че има специална логика в декодера така, че да ги прави в паралел, не съм съвсем сигурен как се отразява на околния код ползващ адресиране през стековия указател. Ще е интересно да се измери как се отразяват на производителността двете конвенции на извикване.
johnfound
Създадено на 01.12.2021, видяно: 1367 пъти. #52035
Аз ползвам асемблер само ако трябва да се пише авх/ссе код. И е много удобно когато стека идва подравнен.
Е, аз това и говоря де – от гледна точка на езиците от високо ниво това е удобно, защото неудобствата са невидими.
Не е съвсем в паралел. При пуш/поп на няколко стойности отместването е с натрупване. И въпреки, че има специална логика в декодера така, че да ги прави в паралел, не съм съвсем сигурен как се отразява на околния код ползващ адресиране през стековия указател. Ще е интересно да се измери как се отразяват на производителността двете конвенции на извикване.
Е, то при поредица push-ове, между тях обикновено няма въобще други инструкции. А даже и когато има, то нямам адресации по esp, защото използвам ebp за достъп до аргументите и локалните променливи.
ДонРеба
Създадено на 01.12.2021, видяно: 1366 пъти. #52039
ами джонка, ако вие асемблеристите си имахте общество, щяхте да си имате и ваши библиотеки и нямада ти дреме за абито на С библиотеките, в чужд манастир със свой устав не се ходи :) като не можете да си направите общество, ще спазвате правилата нанашето, това е :)
johnfound
Създадено на 01.12.2021, видяно: 1363 пъти. #52044
ами джонка, ако вие асемблеристите си имахте общество, щяхте да си имате и ваши библиотеки и нямада ти дреме за абито на С библиотеките, в чужд манастир със свой устав не се ходи :) като не можете да си направите общество, ще спазвате правилата нанашето, това е :)
Прав си на 100%. И не си прав едновременно – библиотеки се пишат. И аз пиша и други хора. За много неща отдавна не използвам C библиотеките.
И разбира се, за някои неща си струва да напишеш библиотеката веднага, за други не си струва въобще.
BIGBUGEX
Създадено на 02.12.2021, видяно: 1333 пъти. #52294
След подробни тестове стигнах до заключението, че двете конвенции са еквивалентни. До такт. Поне на моята машина. Като при цдекл се "размятват" стойности само през мръсните регистри. Тея които се зацапват при извикването. А при стд се пушват стойности релативно адресирани през есп.
Въпрос за C/C++, Linux, крашва ми код, който по-рано работеше.