C and the GNU assembler: how to deal with structs?

Programming, for all ages and all languages.
Alpha
Posts: 15
Joined: Fri Apr 10, 2009 7:04 am
Location: The Netherlands

C and the GNU assembler: how to deal with structs?

Post by Alpha »

Hi everyone,

For my task switching code i have a piece of assembly that has to deal with some structs from C.
In order to access the structs fields, i need to know the right offsets.
Of course i could hard-code them in my assembly, but this means that i have to change everything when i modify a struct in C.
Also i cannot include the struct definitions from C, since that is not understood by the assembler

So my question is: What is a good method to keep these structs synchronised between assembly and C?
I am using the GNU assembler
alexfru
Member
Member
Posts: 1111
Joined: Tue Mar 04, 2014 5:27 am

Re: C and the GNU assembler: how to deal with structs?

Post by alexfru »

AFAIR, you can still use #define in .S files, meaning you can include the same constants in C and assembly. That may not help with everything though.
Other options include: generating both from the same source, using tools to extract debug info out of C/C++ code (e.g. pahole, example).
User avatar
iansjack
Member
Member
Posts: 4690
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: C and the GNU assembler: how to deal with structs?

Post by iansjack »

Probably not the best solution, but I wrote a simple program that reads the C header files and produces corresponding include files for the assembler files. This works out the offsets for structs and in the assembler files you use these offsets in conjunction with a register containing the base address of the struct.
nullplan
Member
Member
Posts: 1773
Joined: Wed Aug 30, 2017 8:24 am

Re: C and the GNU assembler: how to deal with structs?

Post by nullplan »

iansjack's solution is also used by Linux. I'd wager there is no other solution. GNU as has no support for structures of any kind. At the moment, I'm going with manual maintenance, but even at my small project size, it is becoming annoying.
Carpe diem!
User avatar
Solar
Member
Member
Posts: 7615
Joined: Thu Nov 16, 2006 12:01 pm
Location: Germany
Contact:

Re: C and the GNU assembler: how to deal with structs?

Post by Solar »

AVRbeginners.net: Accessing C Structs in Assembler

(iansjack's solution explained, including example code.)
Every good solution is obvious once you've found it.
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

Re: C and the GNU assembler: how to deal with structs?

Post by bzt »

I think that generating C structs into Assembly defines programatically is an unnecessary slow-down in the build process (even when it's just comparing file modification timestamps). From my personal experience, I don't change the structs that much. I personally have thrown out my converter script entirely.

So instead I do the following: I create a separated header file for each shared structs. The same files are then included by both Assembly and C. I start it with the field offset defines, then I have a C struct guarded by an "ifndef _AS" block. That way if I need to change the struct (rarely), then there's only one file to update (not two as with a converter), and I still won't leave out any Assembly reference for sure. Simple, and requires no additional tools.

Example:

Code: Select all

#define tcb_magic 0
#define tcb_flags 4

#ifndef _AS
typedef struct {
    char[4] magic;
    uint32_t flags;
} tcb_t;
#endif
Surplus, you only have to define the field's offsets that are actually referenced from Assembly, meaning if you just add C-related fields to the struct at the end, no need to change the defines.

I haven't changed any of my struct for a while now, so I'm not sure, but maybe if you use gcc to compile your Assembly sources then you can use the "offsetof" in the defines. Depends whether the pre-compiler resolves those before it assigns the define or not.

Cheers,
bzt
Last edited by bzt on Fri Oct 18, 2019 5:15 am, edited 1 time in total.
User avatar
Solar
Member
Member
Posts: 7615
Joined: Thu Nov 16, 2006 12:01 pm
Location: Germany
Contact:

Re: C and the GNU assembler: how to deal with structs?

Post by Solar »

1) Those offsets could change depending on platform, compiler, compiler version, or compiler flags.

2) Never define "magic numbers" in source, especially not if you can derive them.

3) Doing this to avoid one little automatted step in your build system is exactly the kind of micropessimisation that Knuth labelled "the root of all evil". :wink:
Every good solution is obvious once you've found it.
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

Re: C and the GNU assembler: how to deal with structs?

Post by bzt »

Solar wrote:1) Those offsets could change depending on platform, compiler, compiler version, or compiler flags.

2) Never define "magic numbers" in source, especially not if you can derive them.

3) Doing this to avoid one little automatted step in your build system is exactly the kind of micropessimisation that Knuth labelled "the root of all evil". :wink:
Yes, but you know the rules exactly how they could change. If you keep a simple rule: always start with the biggest member, then there would be no surprises.

If you're not dumb or an absolute amateur, then you can easily use packed structs too, aligning the members yourself. It is not THAT hard so it would worth slowing down the build process instead. But if you don't know your compiler, then sure, generate as much files as you want, maybe you miss to update one and failing D.R.Y. would be the "the root of all evil" ;-)

Cheers,
bzt
User avatar
iansjack
Member
Member
Posts: 4690
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: C and the GNU assembler: how to deal with structs?

Post by iansjack »

The point of a make file is to ensure that you don't miss any steps.

I've always thought that computers were there to do the easily defined leg work. Update one file and let the computer do the rest. I guess I'm just lazy.

But each to their own.
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

Re: C and the GNU assembler: how to deal with structs?

Post by bzt »

iansjack wrote:The point of a make file is to ensure that you don't miss any steps.

I've always thought that computers were there to do the easily defined leg work. Update one file and let the computer do the rest. I guess I'm just lazy.

But each to their own.
That's correct.

However in this particular case (from my own experience), let's say at the early development phase,
N times you create a new shared struct
10N times you change one of the shared structs (this is an overestimate)
1000N times you run "make" (or whatever build system you have, and this is an underestimate)

Later, when your task switching code (or whatever part that needs C-Assembly shared structs) is tested and works perfectly,
0 times you create a new shared struct
0 times you change one of the shared structs
10000 times you run "make" (or even more)

So I still wouldn't say slowing down the build process worth it, but go ahead if that fits you. Each to their own.

Cheers,
bzt

ps.: Just for the records, I never had problems with my manually aligned packed structs, and my kernel is multi-platform. I had a problem once, but that's totally unrelated, as the unaligned abort was thrown in C code, and it would have caused problems in Assembly too, generated defines or not. It's not that you can rearrange a packed BPB struct's members.
User avatar
iansjack
Member
Member
Posts: 4690
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: C and the GNU assembler: how to deal with structs?

Post by iansjack »

How does it slow down the build process? I doubt that you could measure the time it takes for make to check whether a header file has changed.
User avatar
eekee
Member
Member
Posts: 880
Joined: Mon May 22, 2017 5:56 am
Location: Kerbin
Discord: eekee
Contact:

Re: C and the GNU assembler: how to deal with structs?

Post by eekee »

If it slows down the build process, the scripting language interpreter is not optimized for the task. For instance, Python infamously takes time to bind symbols before starting to run the script. Or you're feeding a galaxy-size header file to an interpreter which is slow at run-time. In either case, a different interpreter may be fine. I keep coming up with other issues and shooting them down because I can't think of anything that would add more than a 1-second delay to the total build process, or even that much!

The worst-case slow string-processing script experience i ever had was running about a dozen script files totalling about 900 lines as a web server + CMS on a 466MHz PPC running OS X. It was too slow for me to be happy with it serving my web site, but if it had been a compiler I would have thought the compile time was very good! ;) On multicore machines, the speed wasn't an issue at all. OS X was the worst OS for the task by a small margin, Plan 9 was better and Linux better still, but multicore made much more difference. But this is all only relevant to long pipelines which will not be present when running a single interpreter to do a comparatively simple bit of header parsing. It's more comparable to just the markdown processor; just one of the scripts within my worst-case example.

Now I'm tempted to try the Amazing Awk Assembler.
Kaph — a modular OS intended to be easy and fun to administer and code for.
"May wisdom, fun, and the greater good shine forth in all your work." — Leo Brodie
User avatar
~
Member
Member
Posts: 1226
Joined: Tue Mar 06, 2007 11:17 am
Libera.chat IRC: ArcheFire

Re: C and the GNU assembler: how to deal with structs?

Post by ~ »

gcc hello.c -S -save-temps

Code: Select all

#include <stdio.h>

int main(int argc, char **argv)
{

 return 0;
}


hello.s

Code: Select all

	.file	"hello.c"
	.section .text
	.globl	_main
_main:
LFB0:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	movl	$0, %eax
	popl	%ebp
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE0:
	.ident	"GCC: (GNU) 6.1.0"


hello.i

Code: Select all

# 1 "hello.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "hello.c"
# 1 "c:/djgpp/include/stdio.h" 1 3
# 19 "c:/djgpp/include/stdio.h" 3
# 1 "c:/djgpp/include/sys/version.h" 1 3
# 20 "c:/djgpp/include/stdio.h" 2 3
# 1 "c:/djgpp/include/sys/djtypes.h" 1 3
# 43 "c:/djgpp/include/sys/djtypes.h" 3

# 43 "c:/djgpp/include/sys/djtypes.h" 3
typedef short __attribute__((__may_alias__)) __dj_short_a;
typedef int __attribute__((__may_alias__)) __dj_int_a;
typedef long __attribute__((__may_alias__)) __dj_long_a;
typedef long long __attribute__((__may_alias__)) __dj_long_long_a;
typedef unsigned short __attribute__((__may_alias__)) __dj_unsigned_short_a;
typedef unsigned int __attribute__((__may_alias__)) __dj_unsigned_int_a;
typedef unsigned long __attribute__((__may_alias__)) __dj_unsigned_long_a;
typedef unsigned long long __attribute__((__may_alias__)) __dj_unsigned_long_long_a;
typedef float __attribute__((__may_alias__)) __dj_float_a;
typedef double __attribute__((__may_alias__)) __dj_double_a;
typedef long double __attribute__((__may_alias__)) __dj_long_double_a;
# 21 "c:/djgpp/include/stdio.h" 2 3
# 48 "c:/djgpp/include/stdio.h" 3
typedef __builtin_va_list va_list;




typedef long unsigned int size_t;




typedef long signed int ssize_t;







typedef struct {
  ssize_t _cnt;
  char *_ptr;
  char *_base;
  size_t _bufsiz;
  int _flag;
  int _file;
  char *_name_to_remove;
  size_t _fillsize;
} FILE;

typedef unsigned long fpos_t;

extern FILE __dj_stdin, __dj_stdout, __dj_stderr;




void clearerr(FILE *_stream);
int fclose(FILE *_stream);
int feof(FILE *_stream);
int ferror(FILE *_stream);
int fflush(FILE *_stream);
int fgetc(FILE *_stream);
int fgetpos(FILE *_stream, fpos_t *_pos);
char * fgets(char *_s, int _n, FILE *_stream);
FILE * fopen(const char *_filename, const char *_mode);
int fprintf(FILE *_stream, const char *_format, ...);
int fputc(int _c, FILE *_stream);
int fputs(const char *_s, FILE *_stream);
size_t fread(void *_ptr, size_t _size, size_t _nelem, FILE *_stream);
FILE * freopen(const char *_filename, const char *_mode, FILE *_stream);
int fscanf(FILE *_stream, const char *_format, ...);
int fseek(FILE *_stream, long _offset, int _mode);
int fsetpos(FILE *_stream, const fpos_t *_pos);
long ftell(FILE *_stream);
size_t fwrite(const void *_ptr, size_t _size, size_t _nelem, FILE *_stream);
int getc(FILE *_stream);
int getchar(void);
char * gets(char *_s);
void perror(const char *_s);
int printf(const char *_format, ...);
int putc(int _c, FILE *_stream);
int putchar(int _c);
int puts(const char *_s);
int remove(const char *_filename);
int rename(const char *_old, const char *_new);
void rewind(FILE *_stream);
int scanf(const char *_format, ...);
void setbuf(FILE *_stream, char *_buf);
int setvbuf(FILE *_stream, char *_buf, int _mode, size_t _size);
int sprintf(char *_s, const char *_format, ...);
int sscanf(const char *_s, const char *_format, ...);
FILE * tmpfile(void);
char * tmpnam(char *_s);
int ungetc(int _c, FILE *_stream);
int vfprintf(FILE *_stream, const char *_format, va_list _ap);
int vprintf(const char *_format, va_list _ap);
int vsprintf(char *_s, const char *_format, va_list _ap);




int snprintf(char *str, size_t n, const char *fmt, ...);
int vfscanf(FILE *_stream, const char *_format, va_list _ap);
int vscanf(const char *_format, va_list _ap);
int vsnprintf(char *str, size_t n, const char *fmt, va_list ap);
int vsscanf(const char *_s, const char *_format, va_list _ap);
# 143 "c:/djgpp/include/stdio.h" 3
int dprintf(int _fd, const char *_format, ...) __attribute__ ((__format__ (__printf__, 2, 3)));
int fileno(FILE *_stream);
FILE * fdopen(int _fildes, const char *_type);
int mkstemp(char *_template);
int pclose(FILE *_pf);
FILE * popen(const char *_command, const char *_mode);
char * tempnam(const char *_dir, const char *_prefix);
int vdprintf(int _fd, const char *_format, va_list _ap) __attribute__ ((__format__ (__printf__, 2, 0)));



extern FILE __dj_stdprn, __dj_stdaux;





void _djstat_describe_lossage(FILE *_to_where);
int _doprnt(const char *_fmt, va_list _args, FILE *_f);
int _doscan(FILE *_f, const char *_fmt, va_list _args);
int _doscan_low(FILE *, int (*)(FILE *_get), int (*_unget)(int, FILE *), const char *_fmt, va_list _args);
int fpurge(FILE *_f);
int getw(FILE *_f);
char * mktemp(char *_template);
int putw(int _v, FILE *_f);
void setbuffer(FILE *_f, void *_buf, int _size);
void setlinebuf(FILE *_f);
int _rename(const char *_old, const char *_new);
int asprintf(char **_sp, const char *_format, ...) __attribute__((format (__printf__, 2, 3)));
char * asnprintf(char *_s, size_t *_np, const char *_format, ...) __attribute__((format (__printf__, 3, 4)));
int vasprintf(char **_sp, const char *_format, va_list _ap) __attribute__((format (__printf__, 2, 0)));
char * vasnprintf(char *_s, size_t *_np, const char *_format, va_list _ap) __attribute__((format (__printf__, 3, 0)));


typedef int off_t;



__extension__ typedef long long off64_t;


int fseeko(FILE *_stream, off_t _offset, int _mode);
off_t ftello(FILE *_stream);
int fseeko64(FILE *_stream, off64_t _offset, int _mode);
off64_t ftello64(FILE *_stream);
# 2 "hello.c" 2


# 3 "hello.c"
int main(int argc, char **argv)
{

 return 0;
}


Problems include the actual address of each member due to packing.

The best is to always inspect the structure of the produced assembly and produce portable labels to reach the data.

Also, direct the compiler in a way that will always produce code/data exactly at the addresses we physically want in the binary.
YouTube:
http://youtube.com/@AltComp126

My x86 emulator/kernel project and software tools/documentation:
http://master.dl.sourceforge.net/projec ... 7z?viasf=1
User avatar
~
Member
Member
Posts: 1226
Joined: Tue Mar 06, 2007 11:17 am
Libera.chat IRC: ArcheFire

Re: C and the GNU assembler: how to deal with structs?

Post by ~ »

bzt wrote:I think that generating C structs into Assembly defines programatically is an unnecessary slow-down in the build process (even when it's just comparing file modification timestamps). From my personal experience, I don't change the structs that much. I personally have thrown out my converter script entirely.

So instead I do the following: I create a separated header file for each shared structs. The same files are then included by both Assembly and C. I start it with the field offset defines, then I have a C struct guarded by an "ifndef _AS" block. That way if I need to change the struct (rarely), then there's only one file to update (not two as with a converter), and I still won't leave out any Assembly reference for sure. Simple, and requires no additional tools.

Example:

Code: Select all

#define tcb_magic 0
#define tcb_flags 4

#ifndef _AS
typedef struct {
    char[4] magic;
    uint32_t flags;
} tcb_t;
#endif
Surplus, you only have to define the field's offsets that are actually referenced from Assembly, meaning if you just add C-related fields to the struct at the end, no need to change the defines.

I haven't changed any of my struct for a while now, so I'm not sure, but maybe if you use gcc to compile your Assembly sources then you can use the "offsetof" in the defines. Depends whether the pre-compiler resolves those before it assigns the define or not.

Cheers,
bzt
You start modifying in assembly and end up in C.
YouTube:
http://youtube.com/@AltComp126

My x86 emulator/kernel project and software tools/documentation:
http://master.dl.sourceforge.net/projec ... 7z?viasf=1
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

Re: C and the GNU assembler: how to deal with structs?

Post by bzt »

~ wrote:You start modifying in assembly and end up in C.
That's a bad habit you should quit ASAP. The whole point in having both Assembly and C declarations in the same header file is to stop you from doing it separately. Keep your asm defines and C structs synchronized, ALWAYS. You can avoid lots of trouble and endless hours of debugging by doing so.

Cheers,
bzt
Post Reply