PDA

View Full Version : [C] porting e gestione carattere \0


chicco19811
02-10-2017, 12:11
sto facendo un porting di un software in C da HPUX a Linux (premetto che di C conosco poco)

un codice del genere (semplice esempietto da me creato)

char string1[4];
string1[0]='s';
string1[1]='d';
string1[2]='d';
string1[3] = '\0';

char string2[1];
string2[0] = 'a';
printf("\n%s\n", string2);

char string3[2];
string3[0] = 'a';
string3[1] = '\0';
printf("\n%s\n", string3);

sotto LINUX genera

asdd
a

mentre sotto HPUX

a
a

come se sotto Linux, la mancanza del carattere '\0' in string2 "sporcasse" la stessa stringa con il contenuto di string1, cosa che non accade sotto HPUX; quando invece si chiude con '\0' come nel caso di string3, il comportamento è uguale

che mi consigliate?
visto che ho trovato nel codice degli array non chiusi con \0

Grazie

chicco19811
02-10-2017, 12:47
Di mettere \0. Chi ha scritto il codice originario ha scritto codice buggato. Nelle funzioni che accettano stringhe senza specificare la lunghezza delle stesse si dovrebbe SEMPRE passare stringhe NULL terminated.
E anche se il codice su HP-UX 'funziona' c'e' il rischio che questa cattiva programmazione renda il codice scritto insicuro e soggetto ad attacchi che sfruttano il buffer overflow.

quali sono i problemi che si possono avere?
Grazie

chicco19811
02-10-2017, 13:35
Buffer Overflow. L'hai visto anche te. c'e' il rischio che le funzioni utilizzino piu' dati rispetto a quelli del buffer passato.

grazie

fano
02-10-2017, 16:43
Sistema Operativi più "serii" come VxWorks e HP / UX credo "paddassero" automaticamente la memoria con '\0' e quindi questi bug non uscivano fuori... quando facemmo il porting del nostro software da VxWorks a Linux furono dolori visto che andava in SEGFAULT / sbordava di continuo.

Il codice come dice Bellaz89 è bacato visto che la definizione di "stringa" in C è "array di caratteri con '\0' in fondo" però sembra che quei sistemi preferissero coprire questo porcate per permettere a software magari "mission critical" di funzionare comunque...

VxWork per dire non fa una piega nemmeno con questo:


struct my_struct *x;

x = NULL;

printf("%d\n", x->vai_in_core);


il valore stampato era probabilmente "bratta" però sopravviveva, Linux ovviamente ci restava secco :eek:

chicco19811
02-10-2017, 16:58
Sistema Operativi più "serii" come VxWorks e HP / UX credo "paddassero" automaticamente la memoria con '\0' e quindi questi bug non uscivano fuori... quando facemmo il porting del nostro software da VxWorks a Linux furono dolori visto che andava in SEGFAULT / sbordava di continuo.

Il codice come dice Bellaz89 è bacato visto che la definizione di "stringa" in C è "array di caratteri con '\0' in fondo" però sembra che quei sistemi preferissero coprire questo porcate per permettere a software magari "mission critical" di funzionare comunque...

VxWork per dire non fa una piega nemmeno con questo:


struct my_struct *x;

x = NULL;

printf("%d\n", x->vai_in_core);


il valore stampato era probabilmente "bratta" però sopravviveva, Linux ovviamente ci restava secco :eek:

grazie

chicco19811
02-10-2017, 17:51
chiedo un'altra cosa: sui 2 sistemi operativi i tipi di dati differiscono per alcuni aspetti

non potrebbe causare problema questa cosa?
intendo per l'aggiunta (magari anche da parte di alcune funzioni) del '\0'

grazie e scusate se ho scritto boiate

WarDuck
02-10-2017, 18:21
Le stringhe in C sono per definizione array di caratteri terminati con 0.

Il linguaggio C è uno standard, per cui c'entra poco il sistema operativo utilizzato.

Quello che può cambiare in generale può essere:
1) l'implementazione della libreria C (funzioni come printf, strncpy e compagnia bella...)
2) le chiamate di sistema (generalmente wrappate dalla libc)

Se il sistema operativo rispetta lo standard POSIX (adesso SUS) e il programma è scritto per utilizzare funzioni POSIX, non dovrebbero sussistere problemi di portabilità.

@fano: VxWorks, a detta di qualcuno che ha visto il codice sorgente non è proprio da prendere come esempio :).

Il fatto che non fa una piega in un contesto in cui:
1) viene de-referenziato un puntatore NULL (quindi errore palese)
2) dovrebbe essere generata una TRAP

La dice MOOOLTO LUNGA :rolleyes: .

Anzi mi fa pensare in un bug del compilatore, bisognerebbe vedere l'assembly generato.

Evitiamo di dire castronerie e portare ad esempio situazioni palesemente errate in cui per PURA FORTUNA le cose funzionano (MALE).

PS: se uno fa assegnazioni del tipo

char s[] = "pippo";
char *s1 = "pluto";


Il terminatore '\0' viene aggiunto in automatico (in genere questo è vero per tutte le stringhe letterali).

fano
02-10-2017, 18:38
non ho capito, chi padda cosa? :confused:


Così come è possibile per esempio configurare Visual Studio per settare la memoria non usata con valori speciali tipo "0x0BADCAFE" è possibile che HP/UX azzerasse tutta la memoria...


sarei curioso di sapere come... a me sembra una casualita'.

NULL è uguale all'indirizzo 0x00 è una "convenzione" che NON si possa usare, per la CPU non è un "trap", non succede proprio niente se ci scrivi qualcosa o leggi qualcosa da lì, ma di nuovo lo standard C prevede che il programma termini in maniera anomala (SEGFAULT di solito).

@WarDuck
Cerchiamo di capirci non stavo dicendo che VxWork e HP / UX siano dei gioiellini tecnologi per permettere a sti bug di passare inosservati anzi sono pura e semplice bratta!
Ho passato una "vita" con quel m*rdaio di VxWorks che quando "per sbaglio" rilevava un errore andava in "Bus Error" uccidendo l'intero kernel (e via di "bottonata").
E` però evidente che siano stati scritti con filosofie differenti, Linux / libc vanno in core per qualsiasi c*zzata dai...

Purtroppo il nostro amico chicco19811 ora passerà un periodo nel tentativo di trovare tutte le stringhe non tappate "been here, done that"... converrebbe quasi "perdere i sorgenti" e riscrivere tutto da capo in questi casi :D

chicco19811
02-10-2017, 19:47
Purtroppo il nostro amico chicco19811 ora passerà un periodo nel tentativo di trovare tutte le stringhe non tappate "been here, done that"... converrebbe quasi "perdere i sorgenti" e riscrivere tutto da capo in questi casi :D

:cry:

e difatti ho dei bei ORA-01480 (trailing null missing from STR bind value) e, cercando, leggo che "In Pro*C and ORA-01480 may be caused by a lack of the trailing NULL character"

pur da totale ignorante in materia, penso che potrebbe essere un problema collegato

chicco19811
02-10-2017, 22:01
Mi sa che è l'ora di imparare ad usare gdb :Perfido:

lo so già un minimo usare, domani procedo al debug :-)

ho già individuato dove potrebbe essere l'errore guardando il codice

fano
03-10-2017, 08:28
NULL (o 0x0) semplicemente non e' mappato su nessuna pagina associata a nessun processo (almeno nei sistemi operativi mainstream win/linux/osx con supporto alla paginazione). non mi risulta che lo standard C prescriva nulla riguardo 0x0, tant'e' che nei sistemi embedded (senza MMU, e singolo spazio d'indirizzi) con processore ARM e' in genere l'indirizzo da dove leggere il valore di inizalizzazione dello stack pointer. anzi, probabilmente vxworks continuava proprio per qualche motivo simile.

Credo tu abbia ragione la mia versione di VxWorks girava su Motorola 68000 probabilmente senza MMU... infatti è di solito una convenzione che si vada in SEGFAULT.

Io credevo fosse x86 ad andare in fault, ma quest'estate per un bug in Cosmos scrivevamo / leggevamo da 0x00 (null) e correttamente il nostro check segnalava "null pointer exception", ma disabilitando il check apparentemente tutto funzionava la CPU non faceva una piega :eek:

chicco19811
03-10-2017, 10:18
come controllo con sicurezza se l'array termina con '\0'?
trovo varie versioni

grazie

chicco19811
03-10-2017, 11:07
Temo che senza sapere a priori la lunghezza della stringa stessa tu non possa vedere se e' terminata.

e conoscendo la lunghezza?
grazie

Mursey
03-10-2017, 12:09
e conoscendo la lunghezza?
grazie

Si controlla l'ultimo elemento dell'array.

WarDuck
08-10-2017, 11:45
E` però evidente che siano stati scritti con filosofie differenti, Linux / libc vanno in core per qualsiasi c*zzata dai...

Non mi sembra propriamente un'argomentazione tecnica la tua, quanto una sparata :rolleyes: .
Dimmi in quale caso, l'accesso ad una regione di memoria non valida, come nel caso di un NULL pointer dereference, potrebbe essere gestito di default in maniera diversa dalla terminazione del processo.

Nota poi che su Linux il signale SIGSEGV può essere gestito configurando l'apposito signal handler (quindi per esempio potrei pensare di stampare un backtrace).

In generale il problema è di chi scrive codice MALE. Tanto per la cronaca, mi risulta che in tutti i linguaggi anche di alto livello sia considerato un errore un null pointer dereference.

Si può piuttosto discutere del fatto che chi programma dovrebbe attenersi a dei pattern, come ad esempio il pattern RAII, per evitare errori di questo tipo e rendere il codice più leggibile evitando controlli ridondanti.

Ad esempio in C++ posso assumere che il costruttore di un oggetto venga sempre invocato prima di tutti i metodi, quindi se alloco qualcosa lì e l'allocazione va a buon fine, non ho bisogno di controllare in ogni metodo che stia accedendo ad un puntatore non-NULL, perché questa è una invariante di classe.

mi ero perso questo commento. In realta' in C "NULL Pointer dereferencing" e' undefined behaviiour, quindi non c'e' niente. che prescriva che questo errore debba generare una trap a runtime (o equivalenti). Il compilatore sta funzionando a dovere, come il sistema operativo funziona come dovrebbe funzionare, semplicemente non c'e' nessun componente hardware che genera un fault.

È chiaro che l'indirizzo 0 viene mappato dall'MMU su una pagina non valida o con i flag di protezione tali che venga generato un fault ad accedervi. Ma è coerente con il resto del linguaggio, in particolare ad esempio le funzioni di allocazione della memoria come malloc() restituiscono 0 in caso di errore. Quindi si presume che tu debba controllare che l'indirizzo sia comunque diverso da NULL.

In generale poi proprio perché è undefined behavior, dovrebbe essere considerato sempre e comunque un errore a livello logico.

Nei sistemi senza MMU è comunque di solito presente una MPU che può essere configurata opportunamente per avere un comportamento coerente con quello che si aspetta un programmatore C.

Comunque non prendiamocela con gli strumenti quando gli errori vengono introdotti dagli esseri umani.

PS: riguardo l'inizializzazione delle variabili, in genere questo viene fatto a runtime. In particolare la sezione .bss su Linux ad esempio viene azzerata, dunque le variabili che cadono in questa sezione (come ad esempio le variabili globali non inizializzate) assumono valore 0. In generale comunque non si dovrebbe contare troppo su questo fatto.

Variabili sullo stack non inizializzate in particolare possono assumere valori "pseudo-casuali" dovuti a precedenti invocazione a funzione.

fano
10-10-2017, 09:02
Magari possiamo giungere a un compromesso dicendo che il C rende più "facile" scrivere codice brutto e pasticciato... per esempio il fatto che manchi un tipo stringa vero e proprio e che molte funzioni della libc che operano sulle stringhe non rispettino esse stesse il "contratto" che ci deve essere sempre il '\0' in fondo... fa pensare "ma perché io mi devo sbattere?" :D

Per esempio per essere sempre certo che la stringa sia tappata NON si deve usare mai strcpy() (o strncpy() che di tappi ne mette troppi), ma sprintf() o meglio ancora snprintf()!
Oppure se usi gets() vai nell'inferno dei programmatori :eek:

Di fatto l'unica "soluzione" vera è di scriversi un sacco di funzioncine wrapper per rendere il C più safe.. ovvero se volete rendere il C "managed" :D

!fazz
10-10-2017, 15:00
Magari possiamo giungere a un compromesso dicendo che il C rende più "facile" scrivere codice brutto e pasticciato... per esempio il fatto che manchi un tipo stringa vero e proprio e che molte funzioni della libc che operano sulle stringhe non rispettino esse stesse il "contratto" che ci deve essere sempre il '\0' in fondo... fa pensare "ma perché io mi devo sbattere?" :D

Per esempio per essere sempre certo che la stringa sia tappata NON si deve usare mai strcpy() (o strncpy() che di tappi ne mette troppi), ma sprintf() o meglio ancora snprintf()!
Oppure se usi gets() vai nell'inferno dei programmatori :eek:

Di fatto l'unica "soluzione" vera è di scriversi un sacco di funzioncine wrapper per rendere il C più safe.. ovvero se volete rendere il C "managed" :D

e fu così che si reinventò cyclone

fano
10-10-2017, 16:39
Sì trovo Cyclone piuttosto interessante in effetti peccato abbia avuto poca diffusione :(

!fazz
10-10-2017, 16:46
Sì trovo Cyclone piuttosto interessante in effetti peccato abbia avuto poca diffusione :(

Io per nulla, dal mio punto di vista va benissimo che c/c++ non controlli nulla in quanto questo mi permette di avere un codice più veloce il che lavorando su MCU spesso è vitale in quanto sono sempre tirate per il collo all'inverosimile. questo mi permette di mettere i controlli solo dove necessario

se voglio un linguaggio più avanzato sicuramente c non è la scelta migliore se devo utilizzare un accrocchio che mette pezze su pezze snaturando completamente il linguaggio tanto vale utilizzare un linguaggio che prevede nativamente queste funzionalità

fano
11-10-2017, 08:45
[QUOTE=!fazz;45086879
se voglio un linguaggio più avanzato sicuramente c non è la scelta migliore se devo utilizzare un accrocchio che mette pezze su pezze snaturando completamente il linguaggio tanto vale utilizzare un linguaggio che prevede nativamente queste funzionalità
[/QUOTE]

Su questo concordo assolutamente se sei nella "bratta" e devi programmare un processore a 8 bit con 64 KB di RAM beh C magari è fin troppo... quasi il caso di pensare ad assembler :D

Per il resto meglio convertirsi a cosa più moderne tipo Rust o C# :D

Resto dell'idea che almeno K & R ad un vero tipo stringa avrebbero dovuto pensarci niente di complesso o "esotico" semplicemente come fatto in Pascal una struttura con un size_t seguito da un void * / char * e un'intera classe di bug sarebbero "magicamente" spariti.
(Operare sulle stringhe sarebbe stato MOLTO più efficiente pure!)

cdimauro
11-10-2017, 22:09
64KB di RAM? Magari! :D

Certi microcontroller hanno a malapena 2,5KB di RAM a disposizione, e 32KB di flash per il codice: puoi immaginare i salti mortali necessari per farci stare tutto lì. :muro:

Per cui concordo con !fazz. E aggiungo che certe restrizioni che normalmente impongono i s.o. non mi vanno a genio, perché compromettono la creatività del programmatore, e la capacità di risolvere in maniera semplice ed elegante alcune problematiche.

P.S. In ambito embedded non è comune avere a disposizione un'MMU. Per lo meno una PMMU.

fano
15-10-2017, 14:18
Il sistema più "sfigato" su cui ho dovuto lavorare era VxWorks con CPU Motorola 68'000 e 32 MB di RAM! Praticamente un Amiga, ma molto meno divertente :D

cdimauro
15-10-2017, 16:54
Il mio Amiga 2000 aveva un 68000 (a 7Mhz) e 1MB di RAM: praticamente il tuo VxWork era un mostro di sistema. :D

Ma 32MB francamente non mi tornano, visto che il 68000 aveva 16MB di spazio d'indirizzamento totale (estendibile a 64MB utilizzando i famigerati function code. Fattibile, ma solo sulla carta, visto che il 68000 non aveva istruzioni di spostamento dati fra diversi spazi d'indirizzamento coi function code: serviva almeno un 68010 allo scopo).

Quindi immagino che tu abbia avuto almeno un 68020 per gestire tutta quella memoria (ci sarebbe anche il 68012, che è un 68010 con supporto a 1GB di address space, ma è molto raro e ha avuto poca fortuna), che era tutt'altra cosa. :fagiano:

Comunque credimi: ci sono SoC embedded talmente limitati con le risorse (ma nonostante tutto molto diffusi) che un sistema come il tuo sembra fantascienza.

fano
16-10-2017, 12:07
Il mio Amiga 2000 aveva un 68000 (a 7Mhz) e 1MB di RAM: praticamente il tuo VxWork era un mostro di sistema. :D

Ma 32MB francamente non mi tornano, visto che il 68000 aveva 16MB di spazio d'indirizzamento totale (estendibile a 64MB utilizzando i famigerati function code. Fattibile, ma solo sulla carta, visto che il 68000 non aveva istruzioni di spostamento dati fra diversi spazi d'indirizzamento coi function code: serviva almeno un 68010 allo scopo).

Quindi immagino che tu abbia avuto almeno un 68020 per gestire tutta quella memoria (ci sarebbe anche il 68012, che è un 68010 con supporto a 1GB di address space, ma è molto raro e ha avuto poca fortuna), che era tutt'altra cosa. :fagiano:


Non ricordo di preciso quale 68K fosse, ma ricordo che all'inizio avevano 16 MB, ma poi dovettero portarli a 32 MB non so quale "trusco" usarono per farlo... era una versione di VxWorks "pasticciata" visto che il cliente ce l'aveva così mastodontico che aveva pure i sorgenti del kernel / libc... alcuni driver se li avevano scritti loro con risultati beh ridicoli :D
(Tipo che la seriale era più veloce della rete!)


Comunque credimi: ci sono SoC embedded talmente limitati con le risorse (ma nonostante tutto molto diffusi) che un sistema come il tuo sembra fantascienza.

Sì noi chiamiamo "embedded" cose che non lo sono propriamente... comunque talvolta è imbarazzante e poco giustificabile vedere praticamente lo stesso software arrancare su un Atom con 1 GB di RAM... c'è qualcosa di sbagliato non so in X86 o in Linux ma è indubbiamente più pesante il tutto :D

!fazz
16-10-2017, 16:03
64KB di RAM? Magari! :D

Certi microcontroller hanno a malapena 2,5KB di RAM a disposizione, e 32KB di flash per il codice: puoi immaginare i salti mortali necessari per farci stare tutto lì. :muro:

Per cui concordo con !fazz. E aggiungo che certe restrizioni che normalmente impongono i s.o. non mi vanno a genio, perché compromettono la creatività del programmatore, e la capacità di risolvere in maniera semplice ed elegante alcune problematiche.

P.S. In ambito embedded non è comune avere a disposizione un'MMU. Per lo meno una PMMU.

ho per le mani ancora qualcosa di più assurdo, giusto oggi sto mettendo le mani su un progetto basato su un PIC12LF1552 con 2048 word di flash e ben 256 byte di ram vi lascio immaginare i numeri da circo xd

cdimauro
17-10-2017, 06:00
Non ricordo di preciso quale 68K fosse, ma ricordo che all'inizio avevano 16 MB, ma poi dovettero portarli a 32 MB non so quale "trusco" usarono per farlo... era una versione di VxWorks "pasticciata" visto che il cliente ce l'aveva così mastodontico che aveva pure i sorgenti del kernel / libc... alcuni driver se li avevano scritti loro con risultati beh ridicoli :D
(Tipo che la seriale era più veloce della rete!)
Sono ragionevolmente sicuro che si trattasse di almeno un 68020. E ti assicuro: è tanta roba. Così comodo, che potrei svilupparci in assembly anche dei progetti anche di una certa dimensione. Nulla a che vedere con schifezze come PIC et similia: l'assembly 680x0 rimane "da urlo" ancora oggi. :fagiano:
Sì noi chiamiamo "embedded" cose che non lo sono propriamente... comunque talvolta è imbarazzante e poco giustificabile vedere praticamente lo stesso software arrancare su un Atom con 1 GB di RAM... c'è qualcosa di sbagliato non so in X86 o in Linux ma è indubbiamente più pesante il tutto :D
Non credo sia x86 il problema. x86, specialmente in versione 16 bit, è una delle ISA più compatte nonché parca di risorse. Ma anche la versione a 32 bit (80386+) produce codice con una buona densità.

Il problema è... quello che ci devi fare. Ovviamente più il software da sviluppare è complesso, e più risorse richiederà.
ho per le mani ancora qualcosa di più assurdo, giusto oggi sto mettendo le mani su un progetto basato su un PIC12LF1552 con 2048 word di flash e ben 256 byte di ram vi lascio immaginare i numeri da circo xd
Ma LOL Perfino il Commodore Vic 20 era messo meglio. :asd:

In tutta onestà non t'invidio. Con quelle poche risorse molto probabilmente sarai costretto a scrivere codice in assembly. E l'assembly dei PIC è a dir poco rivoltante. Talmente rivoltante che una ventina d'anni fa mi scrissi un assembler di più alto livello, pur di non usare quello standard di Microchip.

Fosse per me, 68020+ tutta la vita. Seconda scelta: 80386.

!fazz
17-10-2017, 10:02
per me una delle microarchietture migliori, una volta capita, è la cortex m4 e la cosa assurda è che è stata rifiutata in quel progetto perchè il micro era troppo costoso (si parla di circa 3€ di differenza su una macchina da minimo 20k€ ) :D

l'm4 ma in generale quasi tutte le archiettture arm based hanno una flessibilità in termini di periferiche e piedinature che semplificano il routing in maniera imbarazzante

cdimauro
18-10-2017, 05:25
per me una delle microarchietture migliori, una volta capita, è la cortex m4 e la cosa assurda è che è stata rifiutata in quel progetto perchè il micro era troppo costoso (si parla di circa 3€ di differenza su una macchina da minimo 20k€ ) :D
Dipende quanti pezzi ne vendono di quelle macchine. :fagiano:

Fino all'ordine di qualche centinaio ha senso poter usare un microcontroller migliore come l'M4, vista la differenza irrisoria e considerato che potrebbe richiedere meno tempo di sviluppo al programmatore.

Ma già arrivando all'ordine delle migliaia la differenza si fa sensibile, e puoi arrivarci a coprire il costo di un dipendente.

Ricominciando a lavorare un po' in area embedded si capisce il perché si facciano delle scelte che a noi sviluppatori sembrano insensate.
Ad esempio l'adozione di economiche memorie eMMC anziché SSD capaci di maggiori cicli di scrittura nella centralina delle automobili: all'azienda è più conveniente pagare uno sviluppatore per un anno intero a ottimizzare le scritture su eMMC, anziché spendere qualche euro in più per un miglior SSD. Anche un solo euro per 3 milioni di auto l'anno fanno 3 milioni euro (l'anno, appunto), mentre lo sviluppatore costa più di un ordine di grandezza di meno e per un solo anno.

Quindi niente: chi lavora nell'embedded è destinato a soffrire, usando poche risorse da rivoltare come un calzino.
l'm4 ma in generale quasi tutte le archiettture arm based hanno una flessibilità in termini di periferiche e piedinature che semplificano il routing in maniera imbarazzante
Infatti da questo punto di vista non ho nulla da dire. D'altra parte è proprio il dominio di ARM.

Prima riguardo a 68K e 386 parlavo esclusivamente come programmatore: mi trovo più a mio agio a scrivere codice assembly con queste architetture, che con ARM (Thumb 2 nel caso dell'M4).
Armv7-M, l'M4 e' il nome del micro.
:D

!fazz
18-10-2017, 18:52
l'ordine è delle migliaia di macchine (non è di certo al livello dei numeri dell'automotive ) il settore è lo stesso ma dall'altra parte della sponda sono macchine da utilizzare nelle linee di assemblaggio :D di cui un 20% con software customizzato ergo .....

@Antonio23 se proprio vogliamo fare i pignoli arm7E-M è il nome dell'architettura , cortex M4 è il nome del core mentre il nome del micro ERA STM32F4[qualcosa]