C kód és endianness

 ( gergov | 2012. február 28., kedd - 14:04 )

Egy alapvetően x86-ra írt programot próbálok lefordítani Openwrt alá big endian processzorra.
A program fordul, részben funcionál is, de bizonyos részei egyszerűen nem működnek.

Elkezdtem keresni a hibát, és a forrást olvasgatva azt látom, hogy teli a kód bitműveletekkel.
Úgy gondolom, egyszerű esetekben a C fordító elrejti előlem a dolgot, például byte-ok &, |, ^, stb műveletei pont úgy működnek, ahogy várnám, de mi van akkor, ha például:

(d[0] << 24) | (d[1] << 16) | (d[2] << 8) | d[3];

ahol d egy uint_8 pointer.
A problémám az, hogy fogalmam sincs, ilyenkor mit csinál a << operátor (szégyellem is magam...).
Ha tippelnem kellene, azt mondanám, egy ilyen shift "átlóg a bytehatárokon". Márpedig akkor a big endian gépen bajban vagyok.
Igazam van?

Köszi a segítséget!

Hozzászólás megjelenítési lehetőségek

A választott hozzászólás megjelenítési mód a „Beállítás” gombbal rögzíthető.

A bithatárokon mindenképp átlóg. :)


tr [:lower:] [:upper:] <<<locsemege
LOCSEMEGE

Byte lesz az, csak megint nem aludtam eleget. Javítom.

http://en.wikipedia.org/wiki/Bitwise_operation

--
NetBSD - Simplicity is prerequisite for reliability

Noha nem tudom a helyes választ, de úgy gondolom, a shiftelésnél mindegy, hiszen lényegtelen, milyen címen helyezed el az MSB-t az LSB-hez képest, a << operátor mindenképpen az MSB felé tolja a biteket. Az indexnél viszont gond lesz, hiszen a d[0] big endian esetén az MSB - a példa eszerint helyes -, míg little endian esetén az LSB, hiszen a kisebb címen szerepel a kisebb helyiértékű byte.


tr [:lower:] [:upper:] <<<locsemege
LOCSEMEGE

Ki kell próbálni:

#include

void main()
{
unsigned char c = 2;
printf("%d\n", ( c << 10 ) );
}

A végeredmény 2048.

Ki kell próbálni:

#include <stdio.h>

void main()
{
  unsigned char c = 2;
  printf("%d\n", ( c << 10 ) );
}

A végeredmény 2048.

Ezzel éppen semmit sem próbáltál ki azon túl, hogy a balra shiftelés balra shiftel.


tr [:lower:] [:upper:] <<<locsemege
LOCSEMEGE

UINT8-at shifteltem 10-zel. Ha a végeredmény UINT8 maradt volna, akkor biztos nem 2048-at írt volna ki.

A megfigyelésem alapján: automatikusan kasztol int-re fölfelé, longra meg csak akkor, ha a shiftelt érték long.
Mindenesetre a szabályt jó volna tudni.

Meg azt is, hogy minden processzoron így van-e.

((int)d[0] << 24) | ((int)d[1] << 16) | ((int)d[2] << 8) | (int)d[3];

megoldás nekem biztonságosabbnak tűnik.

Ez rendben van, de neki nem ez a baja. Ez a mutatvány nem a shiftelésen bukik meg. Ugyanis tljesen mindegy, hogy hol tárolod a memóriában az alsó, s hol a felső byte-ot, a balra shiftelés mindenképpen az alsó byte legmagasabb helyiértékén kipottyanó biteket lépteti be a felső byte aljába.

A gond a címzéssel van, azzal, amikor tömbre hivatkozott, ugyanis big endian esetén a d[0] a legmagasabb helyiértékű byte lesz, míg little endian esetén d[0] a legalacsonyabb helyiértékű byte.


tr [:lower:] [:upper:] <<<locsemege
LOCSEMEGE

Intként kellene beolvasni, utána javítani az endianness-t:

int p = ntohl(*(int *)d);

Ez csak akkor döglik meg, ha 4-gyel nem osztható a cím. Például, ha az int a 23. pozícióban van. Rafkós nyelv ez a C.

Mindenesetre az endian javítására a htonl, ntohl-t kell alkalmazni.

PC-n nem okoz problémát, ha nem 4-gyel osztható határon van az int.

Gondolom az endianness azért jelent neki problémát, mert PC-ről egy másik architektúrára akarja átvinni.
Láttam már gépet, ahol egy int olvasás a 13. pozícióról fagyott.

Persze, nem mindegy az alignment sem, hiszen 4-gyel osztható címről egy 32 bites CPU egy ciklussal elhozza a duplaszót, addig valami idétlen, páratlan címről olvasva ez két ciklust követően shiftelgetés, maszkolgatás, hogy összerakd a 4 byte-ot helyesen. Vagy byte-onként hozod el, akkor röpke 4 ciklus a művelet.


tr [:lower:] [:upper:] <<<locsemege
LOCSEMEGE

Itt nem a shiftelgetés/CPU idő a probléma.

Konkrétan fagyni fog a program bizonyos architektúrákon, méghozzá SIGBUS-sal.

http://en.wikipedia.org/wiki/SIGBUS

Az, hogy az INTEL ezt lenyeli, még semmit sem jelent más architektúrákra nézve. Nem lesz hordozható a kódod.

"Mindenesetre a szabályt jó volna tudni."
http://msdn.microsoft.com/en-us/library/3t4w2bkb%28VS.80%29.aspx

--
NetBSD - Simplicity is prerequisite for reliability

Kipróbáltam gcc-n úgy, hogy a második volt unsigned long (ahánnyal shiftelsz). Az eredmény int lett. Persze MS alatt még működhet máshogy.

A C szabvány sajnos igencsak laza, ahogy esik úgy puffan. Igencsak nehéz rendes többplatformos alkalmazást írni az ilyen huncutságok miatt.

Dehogy nehez.

---
pontscho / fresh!mindworkz

Tudás kell hozzá, hogy egy C alkalmazás rengeteg platformon fusson.

A Java fordítva megy, ahhoz kell tudás, hogy ne fusson minden platform alatt. Láttam már javas remekművet, ami a konfigot a "C:\Windows\myconfig.ini"-ből szedi, de azt szakértő indiaiak írták és igencsak meg kellett küzdeniük, hogy Linux alatt ne fusson.

:)

rossz programot bármilyen nyelven lehet írni.

Abba már bele sem érdemes kötni, hogy mi az a void main ottan.

http://c-faq.com/ansi/voidmain.html
----
Hülye pelikán

http://users.aber.ac.uk/auj/voidmain.shtml

A cikkből egy idézet:

However, a number of beginners' books on C have used void main(void) in all of their examples, leading to a huge number of people who don't know any better.

Voidos deklarációval egy csomóval találkoztam idáig, viszont fagyás egyszer sem volt miatta.

A közeljövőben nem fogom használni, kösz a figyelmeztetést.

A fenti kód egy bájt tömböt alakít 32 bites számmá. Ha fix a platform, akkor meglehet csinálni unionra castolással is, és akkor néhány órajellel gyorsabb lesz :-).

Ezeket:
a. El kell kerülni
b. Ki kell faktorolni konverziós metódusba-metóduskészletbe (lehet inline, ha teljesítménykritikus), amit feltételesen fordítasz.
c. A konverziós metódusokra tesztkészletet kell írni
d. Implementálni minden platformon
* Mégsem kell kifaktorolni és megírni, mert vannak ilyenek készen. Pl lásd man htons
e. Láss csodát, működni fog :-)

Szerk.: $ man endian

> akkor meglehet csinálni unionra castolással is

Az semmit sem segít a big / little endian problémán.

Úgy értem, hogy azon a platformon amin passzol, ott csak 4 bájt másolása a feladat, ott nem kell átforgatni a bájtokat. A másik platformon attól még kell.

A kódból következtetve ez valami fájlformátum vagy hálózati protokoll lehet. Jó a tipp?

Ha jól emlékszem Bit Field-ek esetén gondot okoz a big/little endian különbség. Szerintem a shift-elésnek/maszkolásnak nem kellene problémásnak lennie.

Először is a << végrehajtása előtt "integer promotion" van, azaz a shift -et int -en hajtja végre. Ha az int -be belefér az eredmény (a targeted 32 bit -es int -et használ) akkor ezzel nem lesz baj.
Kérdés, hogy miért byte -onként olvas a kód. Ha a cél a byte sorrend megőrzése volt, akkor semmi baj. Ha nem, akkor szivacs, ugyanis más byte -ot fog felolvasni, mint littleendian gépen olvasott.

Érzésem szerint más lesz itt a baj. Pl "unaligned access", azaz ARM7-ARM9 csak 32 bitre igazított címre tud 32 bites írást/olvasást elvégezni.

Szerk.: bar ha bigendian -re viszed, akkor az nem ARM lesz.

"bar ha bigendian -re viszed, akkor az nem ARM lesz." -- nem mernék rá megesküdni, de mintha az IP-nek lenne egy "lába" (BIGEND; az IP RESET-kor mintavételezi), amin az endianness-t lehet beállítani.

Fuszenecker_Róbert

ARM7 -nel azt hiszem, csak a gyártó állíthatja azaz vagy ilyenre, vagy olyanra állítva gyártja a chip -et. ARM9 -től akár futás közben állítható, ha a gyártó le nem tiltja. Azonban én meg egyetlen olyan rendszert sem láttam, ahol ARM core -t bigendian módban használtak volna. Ettől persze biztosan vannak ilyen rendszerek, csak ritkák mint a fehér holló.

(d[0] << 24) | (d[1] << 16) | (d[2] << 8) | d[3];

Ez egy "big endian to host endian" dekóder. A "d" uint8_t tömbben big endian-ban van leírva a 4*8 octet-es érték, amiből a kifejezés a megfelelő értéket állítja elő (a host ábrázolás lényegtelen, a shift azt csinálja, amit kell).

Ha a "d"-ben nem big endian az ábrázolás, akkor gáz van.

Ahogy Foltos írta, a "d" elemei "signed int"-té lépnek elő a shift-ek előtt. (Az int minimum 16 bites standard C99-ben.) Ha az int 32 bitnél keskenyebb (vagyis <= 24), akkor a túl nagy (== 24) shift miatt egyből undefined behavior. Ha az int 32 bites, és d[0] MSB-je be van kapcsolva (0x80), akkor undefined behavior.

Ezennel megfogadom, hogy legközelebb jobban megnézem, mit kérdezek, és akkor nem kérdezek hülyeséget... :)

Az van, hogy a bemásolt kifejezés egy return jobb oldalán van egy olyan függvényben, aminek uint32_t a visszatérési értéke.
Amit én a kicsit gusztustalan kódformázás miatt elsőre lazán uint8_t-nek néztem. Így aztán nagyon nem értettem, hogy ennek így mi az értelme, hiszen a << 24 elvileg messze túlcsordulna, és akkor már minek össze | -olni. Olyan beteg gondolatom támadt, hogy esetleg valami ronda mellékhatást használ ki a kód írója.

Valójában az uint32_t-vel már nagyon is van értelme a dolognak, sőt, szerintem az architektúrától függetlenül jó eredményt kellene adnia, és első blikkre épp a byteorder megfordítása volt a cél. Amit én most elvileg megkerülhetek.

A targetem egyébként 32 bites MIPS, a szoftver pedig a nagy kedvencem tvheadend. A kiemelt rész, ha minden igaz a matroska muxerből egy darabka. (Ez volt az első fájl, amibe belenéztem.)
A program a makefileok minimális pofozgatásával lefordul, fut is, működik a webes felület, felismeri a tunerem, sőt, még a multiplexeken lévő service-eket is megtalálja. Csak valahogy a stream felbontásán akad fenn. Konkrétan a csatornanevek, PID-k, miegyebek kibontása már nem működik. Ezért gyanakszom arra, hogy valami hasonló mágia okozza a gondot, ami nem architektúrafüggetlen.

A teljes program egyébként nem túl sok, alig párszáz kilobyte. Valószínű az lesz, hogy végignézem, és ami gázosnak tűnik, azt kipróbálom, hogy ugyanazt az eredményt adja-e mindkét platformon. Ha találok valamit, ami nem, akkor megvan a hiba.

Szórd tele debug printekkel. Két vas kimenetet hasonlítsd össze.

Elvileg van valami #define, ami megmondja a byte sex-et:

1. http://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html (Ctrl+F: endian)
2. http://gcc.gnu.org/ml/gcc-help/2007-07/msg00342.html

Na, szóval, sikerült megtalálni a hibát.
Nem a bitműveletek okoznak gondot, hanem egy karakterkódolási disznóság történik. Vicces, hogy ez a program felének működését képes agyonütni.
Van egy ilyen hívás az egyik fájlban: convert_latin1 = dvb_iconv_open("ISO6937");
Ez a routeren -1-el tér vissza, az x86_64-es gépemen mindig más pozitív egész számmal, ami gondolom, egy azonosító lehet.

Annyit sikerült kiderítenem, hogy az iconv implementáció a glibc része. Ebből az openwrt-hez jelenleg 2.6.13-ast használok.
Ez az -enable-add-ons=localedata -ra azt üzeni vissza, hogy deprecated.

Valaki tudna segíteni abban, hogy hogyan lehet ezt "megjavítani"? Köszi!

http://en.wikipedia.org/wiki/ISO/IEC_6937

A dvb_iconv_open() valszeg egy wrapper az iconv_open() körül, ami paraméterül fogadja a forrás charset-et, és bele van drótozva a latin1 target charset-ként. A visszaadott érték egy conversion descriptor, amit az iconv()-val kell használni.

Sejtésem: a 2.6.13-as glibc-ben még nem volt ISO6937 <-> UCS-4 konverter. (A glibc iconv()-ja az összes charset-et UCS-4-re ill. -ről konvertálja először; "köztes kódolás".) Könnyen tudod így ellenőrizni:

ls -l /usr/lib*/gconv/ISO_6937*.so

... Szinte biztos vagyok benne, hogy ezek a file-ok már bőven részét képezték a 2.6-os sorozatnak. Nálad valószínűleg nincsenek telepítve, vagy futás közben nem érhetők el.

http://www.gnu.org/software/libc/manual/html_node/glibc-iconv-Implementation.html

Ja, igen, megint elszúrtam. A dvb_iconv_open() lemaradt, de nagyon közel jártál az igazsághoz. Ennyi van csak benne:

static iconv_t
dvb_iconv_open(const char *srcencoding)
{
  iconv_t ic;
  ic = iconv_open("UTF-8", srcencoding);
  return ic;
}

A gconv-ból hiányzó fájlok telitalálat, ugyanis a routeren ilyen mappa egyáltalán nincs. Ez alapján már megtaláltam, hogyan lehet ezt a problémát megoldani (az openwrt menuconfigjában van egy opció, ahol be lehet kapcsolni a teljes nyelvi támogatást).
Köszönöm! Most fordul az új változat, remélem, végre fog működni.

Fordítás után manuálisan bemásoltam az ISO* és UTF* .so fájlokat a router /lib/gconv mappájába. Tökéletesen működik. Köszönöm!

Még egy olyan mellékes kérdésem lenne, hogy hogyan lehetne ízléses formában összecsomagolni az összes OpenWrt-re és magára a programra vonatkozó patchet? Csak mert a google szerint már más is próbálkozott a fordítással, és nem jött neki össze.

Az ideális az, ha a módosításokat egy logikai felbontású patch series-ben tálalod. Nem tudom, a jelenlegi változtatásaid hogyan készültek el, de ha csak egy módosított (eredetileg egy tar.gz-ből kibontott, majd helyben hákolt) fád van, akkor:

Első lépésként egy diff -Nurp paranccsal készíthetsz egy irdatlan méretű patch-et (egy párhuzamos, más nevű könyvtárba bontsd ki előbb ugyanazt a tarballt).

Második lépésként klónozd le a projekt git repo-ját, csekkold ki azt a verziót, amelyből a tarball is készült (ekkor a nagy patch-nek pontosan kell illeszkednie), majd ebből a verzióból ágaztass le egy lokális branch-et, majd erre húzd rá a nagy patch-et, és commit-old is be az új branch-re.

Harmadik lépésként a git rebase-t használva ezt a mammut changeset-et fogod feldarabolni. Itt egy jó leírás: http://clalance.blogspot.com/2009/10/splitting-patches-with-git.html

"csekkold ki azt a verziót" tag-et.

Egyebkent igen, bar a git add -p nem mindig ad jo megoldast. Erdemes nezni valami kulturalt git gui-t, Linuxra ilyen pl. a gitg, ahol lehet a patch hozzaado algoritmus erzekenyseget is allitani, es akkor pontosan lehet latni, hogy hol vannak a hunk-hatarok.
--
Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal

Ez az utasítás felteszi, hogy az adat big-endian-ban van. Ez önmagában nem kell hibát okozzon, ha egy olyan platformra migrálsz, ami amúgyis big-endian érvényesül.