mboost-dp1

Blast from the past


Gå til bund
Gravatar #1 - arne_v
1. maj 2023 17:02
Der var en som havde problemer med varargs.h - det er pre-C89 aka K&R C.

Lidt undersøgelse viste at problemet skyldes brug af varargs.h med kode som virker med stdarg.h - de ligner hinanden men er *ikke* kompatible.

$ type va1.c
#include <stdio.h>
#include <stdarg.h>

void test(int n, ...)
{
int i;
va_list ap;
va_start(ap, n);
for(i = 0; i < n; i++)
{
printf("%d : %s\n", i, va_arg(ap, char *));
}
va_end(ap);
}

int main(int argc, char *argv[])
{
test(1, "A");
test(2, "A", "BB");
test(3, "A", "BB", "CCC");
return 0;
}
$ cc va1
$ link va1
$ run va1
0 : A
0 : A
1 : BB
0 : A
1 : BB
2 : CCC
$ type va2.c
#include <stdio.h>
#include <varargs.h>

void test(n, va_alist)
int n;
va_dcl
{
int i;
va_list ap;
va_start(ap);
for(i = 0; i < n; i++)
{
printf("%d : %s\n", i, va_arg(ap, char *));
}
va_end(ap);
}

int main(int argc, char *argv[])
{
test(1, "A");
test(2, "A", "BB");
test(3, "A", "BB", "CCC");
return 0;
}
$ cc va2
$ link va2
$ run va2
0 : A
0 : A
1 : BB
0 : A
1 : BB
2 : CCC


Gravatar #2 - fnaf12
4. maj 2023 07:45
The Mega Pizzaplex is the setting for Freddy Fazbear's fnaf security breach, a survival horror game. You will play the part of Gregory, a boy who has been stuck in his house all night. When the surprisingly strong Pizzaplex lockout goes into effect, you have to stay alive and look for a way out. Now, let's take a look at how to play Security Breach.
Gravatar #3 - larsp
8. maj 2023 10:00
#1 Lusket. Jeg var ikke klar over at der var forskellige varargs varianter. Er der en grund til at va2.c benytter gammeldags funktions deklaration?

Jeg har personligt holdt mig fra at bruge varargs i egenudviklet C kode, med meget få undtagelser. C er lowlevel og uden sikkerhedsnet, så det er på sin plads at holde tingene så simple og enkle som muligt, så man nærmest kan se maskinkoden under hver operation. Det synes jeg ikke man kan med varargs. Hvad sker der f.eks. hvis va_end ikke bliver kaldt?
Gravatar #4 - arne_v
8. maj 2023 12:41
larsp (3) skrev:

#1 Lusket. Jeg var ikke klar over at der var forskellige varargs varianter.


K&R C og ANSI/ISO C.

larsp (3) skrev:

Er der en grund til at va2.c benytter gammeldags funktions deklaration?


Ja. Det er sådan det API er defineret.

va_dcl skal virke der og vil ikke nødvendigvis virke andre steder.

Faktisk va_dcl implementation varierer.

#define va_dcl

er naturligvis ikke noget problem.

Men:

#define va_dcl int va_alist;

vil give en syntax fejl ved brug i standard ANSI/ISO prototype p.g.a. semikolon.

larsp (3) skrev:

Jeg har personligt holdt mig fra at bruge varargs i egenudviklet C kode, med meget få undtagelser.


Jeg har aldrig brugt varargs - så gammel er jeg trods alt ikke.

Men jeg har brugt stdarg en hel del i både C og C++. Specielt lige omkring år 2000 var min C++ kode fuld af stdarg.

larsp (3) skrev:

C er lowlevel og uden sikkerhedsnet, så det er på sin plads at holde tingene så simple og enkle som muligt, så man nærmest kan se maskinkoden under hver operation. Det synes jeg ikke man kan med varargs. Hvad sker der f.eks. hvis va_end ikke bliver kaldt?


Per standard:

"if the va_end macro is not invoked before the return, the behavior is undefined."

Gravatar #5 - arne_v
8. maj 2023 13:04
arne_v (4) skrev:

Jeg har aldrig brugt varargs - så gammel er jeg trods alt ikke.


Jeg er gammel nok til at have hørt om nogle af problemerne i K&R C.

Den klassiske er compound operatorer.

ANSI/ISO C bruger op= mens K&R C brugte =op.

Det gav nogle tricky syntaxer.

v =- 1; ==> v = v - 1;
v = -1; ==> v = (-1);
v=-1; ==> ????
v = - 1; ==> ????


Gravatar #6 - larsp
8. maj 2023 19:39
Aha, varargs er helt tilbage fra K&R C.
arne_v (4) skrev:
larsp (3) skrev:

Jeg har personligt holdt mig fra at bruge varargs i egenudviklet C kode, med meget få undtagelser.


Jeg har aldrig brugt varargs - så gammel er jeg trods alt ikke.

Men jeg har brugt stdarg en hel del i både C og C++. Specielt lige omkring år 2000 var min C++ kode fuld af stdarg.

Jeg mente variabelt antal argumenter generelt i C.

Når man giver en funktion argumenter i C bør man tilstræbe at give dem type definitioner som compileren kan checke. Det er vel ånden i et strongly typed sprog. Med var/stdarg er der ikke definitioner på de ekstra argumenter, så det er lidt i samme kategori som at kaste rundt med void pointere.

Hvis man vil give en funktion en dynamisk liste af ting, ville jeg deklarere noget der repræsenterer listen og overføre det. Jeg har aldrig grebet fat i var/stdarg til den slags. Eneste undtagelse er custom printf-agtige funktioner.
arne_v (5) skrev:
ANSI/ISO C bruger op= mens K&R C brugte =op.

Det gav nogle tricky syntaxer.

v =- 1; ==> v = v - 1;
v = -1; ==> v = (-1);
v=-1; ==> ????
v = - 1; ==> ????

Damn. K&R C var godt nok en grim ælling ... der er voksede op til en ikke alt for køn svane ;)
Gravatar #7 - arne_v
8. maj 2023 23:44
larsp (6) skrev:

arne_v (4) skrev:

Men jeg har brugt stdarg en hel del i både C og C++. Specielt lige omkring år 2000 var min C++ kode fuld af stdarg.

Jeg mente variabelt antal argumenter generelt i C.

Når man giver en funktion argumenter i C bør man tilstræbe at give dem type definitioner som compileren kan checke. Det er vel ånden i et strongly typed sprog. Med var/stdarg er der ikke definitioner på de ekstra argumenter, så det er lidt i samme kategori som at kaste rundt med void pointere.

Hvis man vil give en funktion en dynamisk liste af ting, ville jeg deklarere noget der repræsenterer listen og overføre det. Jeg har aldrig grebet fat i var/stdarg til den slags. Eneste undtagelse er custom printf-agtige funktioner.


Det kniber meget med type sikkerheden, men det gør det jo nogengang i C/C++.

Men jeg var som sagt meget glad for det dengang.

Det er endda særdeles anvendeligt, hvis man skal lave et generisk API for nogle vidt forskellige API'er.

Eksempel.

3 vidt forskellige database API:


#include <cstdio>
#include <cstdarg>

#include <iostream>

using namespace std;

class BadDB
{
public:
void exec(const char *sql) { cout << sql << endl; }
};

class OkayDB
{
public:
void prep(const char *sql) { cout << sql << ":" << endl; }
void bind(const char *p) { cout << " " << p << endl; }
void exec() { }
};

class GoodDB
{
public:
void exec(const char *sql, int n, ...);
void vexec(const char *sql, int n, va_list ap);
};

void GoodDB::exec(const char *sql, int n, ...)
{
va_list ap;
va_start(ap, n);
vexec(sql, n, ap);
va_end(ap);
}

void GoodDB::vexec(const char *sql, int n, va_list ap)
{
cout << sql << ":" << endl;
for(int i = 0; i < n; i++)
{
cout << " " << va_arg(ap, const char *) << endl;
}
}

static const char *sqltmplt[] = { NULL, "INSERT INTO t VALUES('%s')", "INSERT INTO t VALUES('%s','%s')", "INSERT INTO t VALUES('%s','%s','%s')" };

int main()
{
BadDB *db1 = new BadDB();
char sql[1000];
sprintf(sql, sqltmplt[1], "A");
db1->exec(sql);
sprintf(sql, sqltmplt[2], "A", "BB");
db1->exec(sql);
sprintf(sql, sqltmplt[3], "A", "BB", "CCC");
db1->exec(sql);
delete db1;
OkayDB *db2 = new OkayDB();
db2->prep("INSERT INTO t VALUES(?)");
db2->bind("A");
db2->exec();
db2->prep("INSERT INTO t VALUES(?,?)");
db2->bind("A");
db2->bind("BB");
db2->exec();
db2->prep("INSERT INTO t VALUES(?,?,?)");
db2->bind("A");
db2->bind("BB");
db2->bind("CCC");
db2->exec();
delete db2;
GoodDB *db3 = new GoodDB();
db3->exec("INSERT INTO t VALUES(?)", 1, "A");
db3->exec("INSERT INTO t VALUES(?,?)", 2, "A", "BB");
db3->exec("INSERT INTO t VALUES(?,?,?)", 3, "A", "BB", "CCC");
delete db3;
return 0;
}


Med et generisk API ovenpå:


#include <cstdio>
#include <cstdarg>

#include <iostream>
#include <string>

using namespace std;

class BadDB
{
public:
void exec(const char *sql) { cout << sql << endl; }
};

class OkayDB
{
public:
void prep(const char *sql) { cout << sql << ":" << endl; }
void bind(const char *p) { cout << " " << p << endl; }
void exec() { }
};

class GoodDB
{
public:
void exec(const char *sql, int n, ...);
void vexec(const char *sql, int n, va_list ap);
};

void GoodDB::exec(const char *sql, int n, ...)
{
va_list ap;
va_start(ap, n);
vexec(sql, n, ap);
va_end(ap);
}

void GoodDB::vexec(const char *sql, int n, va_list ap)
{
cout << sql << ":" << endl;
for(int i = 0; i < n; i++)
{
cout << " " << va_arg(ap, const char *) << endl;
}
}

class XDB
{
public:
void exec(const char *sql, int n, ...);
virtual void vexec(const char *sql, int n, va_list ap) = 0;
};

void XDB::exec(const char *sql, int n, ...)
{
va_list ap;
va_start(ap, n);
vexec(sql, n, ap);
va_end(ap);
}

class BadXDB : public XDB
{
private:
BadDB *db;
public:
BadXDB(BadDB *db) { this->db = db; }
virtual ~BadXDB() { delete db; }
virtual void vexec(const char *sql, int n, va_list ap);
};

void BadXDB::vexec(const char *sql, int n, va_list ap)
{
string sql2 = sql;
for(int i = 0; i < n; i++)
{
size_t ix = sql2.find_first_of("?");
sql2.replace(ix, 1, "'" + string(va_arg(ap, const char *)) + "'");
}
db->exec(sql2.c_str());
}

class OkayXDB : public XDB
{
private:
OkayDB *db;
public:
OkayXDB(OkayDB *db) { this->db = db; }
virtual ~OkayXDB() { delete db; }
virtual void vexec(const char *sql, int n, va_list ap);
};

void OkayXDB::vexec(const char *sql, int n, va_list ap)
{
db->prep(sql);
for(int i = 0; i < n; i++)
{
db->bind(va_arg(ap, const char *));
}
db->exec();
}

class GoodXDB : public XDB
{
private:
GoodDB *db;
public:
GoodXDB(GoodDB *db) { this->db = db; }
virtual ~GoodXDB() { delete db; }
virtual void vexec(const char *sql, int n, va_list ap) { db->vexec(sql, n, ap); }
};

int main()
{
XDB *db1 = new BadXDB(new BadDB());
db1->exec("INSERT INTO t VALUES(?)", 1, "A");
db1->exec("INSERT INTO t VALUES(?,?)", 2, "A", "BB");
db1->exec("INSERT INTO t VALUES(?,?,?)", 3, "A", "BB", "CCC");
delete db1;
XDB *db2 = new OkayXDB(new OkayDB());
db2->exec("INSERT INTO t VALUES(?)", 1, "A");
db2->exec("INSERT INTO t VALUES(?,?)", 2, "A", "BB");
db2->exec("INSERT INTO t VALUES(?,?,?)", 3, "A", "BB", "CCC");
delete db2;
XDB *db3 = new GoodXDB(new GoodDB());
db3->exec("INSERT INTO t VALUES(?)", 1, "A");
db3->exec("INSERT INTO t VALUES(?,?)", 2, "A", "BB");
db3->exec("INSERT INTO t VALUES(?,?,?)", 3, "A", "BB", "CCC");
delete db3;
return 0;
}


Jeg presser stdarg citronen til det yderste!
Gravatar #8 - larsp
9. maj 2023 13:20
#7 Jeg kan godt følge idéen og det er vel en art SQL format print teknik.

Jeg har netop også netop lavet printf agtige konstruktioner med variabel args, f.eks. til særlige logging formål hvor format print er meget praktisk.

Men i den dårlige API bruges sprintf til en statisk buffer, (det burde have været snprinf!!) hvor de "bedre" APIer tager sig frihed til dynamiske C++ strenge, så det er lidt unfair.

Hvad sker der hvis man jogger i spinaten og laver en ? for lidt:

size_t ix = sql2.find_first_of("?");
sql2.replace(ix, 1, "'" + string(va_arg(ap, const char *)) + "'");

... det bliver vel til noget segfault
Gravatar #9 - arne_v
9. maj 2023 13:39
larsp (8) skrev:

#7 Jeg kan godt følge idéen og det er vel en art SQL format print teknik.

Jeg har netop også netop lavet printf agtige konstruktioner med variabel args, f.eks. til særlige logging formål hvor format print er meget praktisk.


Ja.

Men en af pointerne er at man ved brug af overload med en ... version og en va_list version kan arbejde bekvemt med flere lag.

larsp (8) skrev:

Men i den dårlige API bruges sprintf til en statisk buffer, (det burde have været snprinf!!) hvor de "bedre" APIer tager sig frihed til dynamiske C++ strenge, så det er lidt unfair.


Korrekt. Der burde være noget beskyttelse der.

larsp (8) skrev:

Hvad sker der hvis man jogger i spinaten og laver en ? for lidt:

size_t ix = sql2.find_first_of("?");
sql2.replace(ix, 1, "'" + string(va_arg(ap, const char *)) + "'");

... det bliver vel til noget segfault


Ja.

find_first_of returnerer den størst mulige værdi af size_t (string::npos er et pænt navn) hvis intet match og det giver formentligt en eller anden grim fejl.

Så der burde naturligvis testes på return værdien.
Gravatar #10 - arne_v
17. maj 2023 00:46
#6

Men det var også en anden tid dengang.

Går man bare få år længere tilbage var det almindeligt med systemer uden virtuel memory.

Nu tænker mange jo straks at det "bare" betyder mindre memory man kan bruge men ellers ingen forskel.

Men det betyder mere end bare det.

Der er f.eks. ikke noget der hedder readonly data i ikke-virtuel memory. Så hvis man begynder at overskrive noget der burde være readonly som f.eks. konstanter så kan ens program opføre sig meget mystisk.

Det kan simuleres ved at ændre en readonly sektion til writeable.

Eksempel:

$ type uc.for
program uc
call s1(1)
call s2(1)
end
c
subroutine s1(v)
integer*4 v
v = 2
return
end
c
subroutine s2(v)
integer*4 v
write(*,*) v
return
end
$ for uc
$ link uc + sys$input/opt
PSECT_ATTR=$LINK$,wrt
$
$ run uc
2

(Fejlen kan nemmere forekomme i Fortran end i C da default er call by reference i Fortran også for konstanter)
Gravatar #11 - larsp
17. maj 2023 12:45
#10

$ for
bash: syntax error near unexpected token `newline'

Heh, 'for' er vist ikke måden at compile fortran på i Linux.

Nå men jeg fik det compilet med gfortran og får resultatet:
$ ./a.out
Program received signal SIGSEGV: Segmentation fault - invalid memory reference.

Backtrace for this error:
#0 0x7f99e61d4ad0 in ???
#1 0x7f99e61d3c35 in ???
#2 0x7f99e5fcb51f in ???
at ./signal/../sysdeps/unix/sysv/linux/x86_64/libc_sigaction.c:0
#3 0x562fbdd03202 in ???
#4 0x562fbdd0321d in ???
#5 0x562fbdd03268 in ???
#6 0x7f99e5fb2d8f in __libc_start_call_main
at ../sysdeps/nptl/libc_start_call_main.h:58
#7 0x7f99e5fb2e3f in __libc_start_main_impl
at ../csu/libc-start.c:392
#8 0x562fbdd030b4 in ???
#9 0xffffffffffffffff in ???
Segmentation fault (core dumped)

(kørt uden PSECT_ATTR=$LINK$,wrt som jeg ikke forstår)

arne_v (10) skrev:
Der er f.eks. ikke noget der hedder readonly data i ikke-virtuel memory. Så hvis man begynder at overskrive noget der burde være readonly som f.eks. konstanter så kan ens program opføre sig meget mystisk.

Det kan simuleres ved at ændre en readonly sektion til writeable.

Jeg har aldrig tænkt på at moderne platforme kan skrive-beskytte readonly data segmenter vha. memory mapperen. Under alle omstændigheder er vildfarne pointere noget der aldrig må ske og som fører fuldstændig crash. Jo før der segfaultes, jo bedre, så jeg kan godt se idéen.

Jeg prøvede lige og du har ret:


#include <stdio.h>

const int test=1;

int main()
{
printf("Test: %d\n", test);
int *p = &test; // warning ignoreres her
*p = 2;
printf("Test: %d\n", test);
return 0;
}



$ ./a.out
Test: 1
Segmentation fault (core dumped)

Gravatar #12 - arne_v
17. maj 2023 13:18
larsp (11) skrev:
#10

$ for
bash: syntax error near unexpected token `newline'

Heh, 'for' er vist ikke måden at compile fortran på i Linux.


VMS bruger for(tran).

GCC bruger gfortran (dette årtusind) eller g77 (forrige årtusind).

larsp (11) skrev:

Nå men jeg fik det compilet med gfortran og får resultatet:
$ ./a.out
Program received signal SIGSEGV: Segmentation fault - invalid memory reference.
...
Segmentation fault (core dumped)



Fordi 1 er blevet anbragt i en section som er readonly, hvilket giver en fejl når man forsøger at skrive til den.

larsp (11) skrev:

(kørt uden PSECT_ATTR=$LINK$,wrt som jeg ikke forstår)


VMS fortran putter 1 i en sektion med navn $LINK$ om den lille tilføjelse til link kommandoen beder linkeren ændre $LINK$ sektion fra readonly til writeable - for at simulere gamle dage.
Gravatar #13 - arne_v
17. maj 2023 13:54
larsp (11) skrev:


#include <stdio.h>

const int test=1;

int main()
{
printf("Test: %d\n", test);
int *p = &test; // warning ignoreres her
*p = 2;
printf("Test: %d\n", test);
return 0;
}



$ ./a.out
Test: 1
Segmentation fault (core dumped)



Det kan godt laves i C også.

En modificeret udgave af din kode:

$ type uc2.c
#include <stdio.h>

void s1(int *v)
{
*v = 2;
}

void s2(int *v)
{
printf("%d\n", *v);
}

const int v = 1;

int main()
{
s1(&v);
s2(&v);
return 0;
}

$ cc uc2

s1(&v);
.......^
%CC-W-NOTCONSTQUAL, In this statement, the referenced type of the pointer value "&v" is const, but the referenced type of the target
of this assignment is not.
at line number 17 in file DISK2:[ARNE]uc2.c;1

s2(&v);
.......^
%CC-W-NOTCONSTQUAL, In this statement, the referenced type of the pointer value "&v" is const, but the referenced type of the target
of this assignment is not.
at line number 18 in file DISK2:[ARNE]uc2.c;1
$ link uc2
%LINK-W-WRNERS, compilation warnings
in module UC2 file DISK2:[ARNE]uc2.OBJ;2
$ run uc2
%SYSTEM-F-ACCVIO, access violation, reason mask=04, virtual address=00000000000100C0, PC=0000000000020100, PS=0000001B
%TRACE-F-TRACEBACK, symbolic stack dump follows
image module routine line rel PC abs PC
uc2 UC2 s2 1507 0000000000000100 0000000000020100
uc2 UC2 1520 0000000000020100 0000000000000000
0 FFFFFFFF80340964 FFFFFFFF80340964
%TRACE-I-END, end of TRACE stack dump
$ cc uc2

s1(&v);
.......^
%CC-W-NOTCONSTQUAL, In this statement, the referenced type of the pointer value "&v" is const, but the referenced type of the target
of this assignment is not.
at line number 17 in file DISK2:[ARNE]uc2.c;1

s2(&v);
.......^
%CC-W-NOTCONSTQUAL, In this statement, the referenced type of the pointer value "&v" is const, but the referenced type of the target
of this assignment is not.
at line number 18 in file DISK2:[ARNE]uc2.c;1
$ link uc2 + sys$input/opt
PSECT_ATTR=V,wrt
%LINK-W-WRNERS, compilation warnings
in module UC2 file DISK2:[ARNE]uc2.OBJ;3
$
$ run uc2
2

Er helt som forventet.

Normal build resulterer i (hvis man ignorerer warnings):

%SYSTEM-F-ACCVIO, access violation, reason mask=04, virtual address=00000000000100C0, PC=0000000000020100, PS=0000001B

Hvilket læses som at instruktionen på adresse 0000000000020100 har forsøgt at skrive til adressen 00000000000100C0 og det har den ikke adgang til.

Men med det lille link trick:

PSECT_ATTR=V,wrt

skriver den glad og fro 2 ud, fordi nu er den memory lokation blevet gjordt skrivbart.

Bemærk at der er ikke noget VMS specifikt i teknikken som sådan.

Det eneste VMS specifikke er at jeg ved hvordan man kan bede VMS linkeren ændre attributter på en vilkårlig memory sektion - hvis jeg vidste hvordan man gjorde det med GCC linkeren så ville det fungere lige sådan.

Gå til top

Opret dig som bruger i dag

Det er gratis, og du binder dig ikke til noget.

Når du er oprettet som bruger, får du adgang til en lang række af sidens andre muligheder, såsom at udforme siden efter eget ønske og deltage i diskussionerne.

Opret Bruger Login