C and the GNU assembler: how to deal with structs?
C and the GNU assembler: how to deal with structs?
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
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
Re: C and the GNU assembler: how to deal with structs?
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.
Re: C and the GNU assembler: how to deal with structs?
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!
Re: C and the GNU assembler: how to deal with structs?
AVRbeginners.net: Accessing C Structs in Assembler
(iansjack's solution explained, including example code.)
(iansjack's solution explained, including example code.)
Every good solution is obvious once you've found it.
Re: C and the GNU assembler: how to deal with structs?
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:
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
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
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.
Re: C and the GNU assembler: how to deal with structs?
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".
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".
Every good solution is obvious once you've found it.
Re: C and the GNU assembler: how to deal with structs?
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.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".
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
Re: C and the GNU assembler: how to deal with structs?
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.
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.
Re: C and the GNU assembler: how to deal with structs?
That's correct.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.
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.
Re: C and the GNU assembler: how to deal with structs?
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.
Re: C and the GNU assembler: how to deal with structs?
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.
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
"May wisdom, fun, and the greater good shine forth in all your work." — Leo Brodie
Re: C and the GNU assembler: how to deal with structs?
gcc hello.c -S -save-temps
hello.s
hello.i
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.
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
http://youtube.com/@AltComp126
My x86 emulator/kernel project and software tools/documentation:
http://master.dl.sourceforge.net/projec ... 7z?viasf=1
Re: C and the GNU assembler: how to deal with structs?
You start modifying in assembly and end up in C.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: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.Code: Select all
#define tcb_magic 0 #define tcb_flags 4 #ifndef _AS typedef struct { char[4] magic; uint32_t flags; } tcb_t; #endif
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
YouTube:
http://youtube.com/@AltComp126
My x86 emulator/kernel project and software tools/documentation:
http://master.dl.sourceforge.net/projec ... 7z?viasf=1
http://youtube.com/@AltComp126
My x86 emulator/kernel project and software tools/documentation:
http://master.dl.sourceforge.net/projec ... 7z?viasf=1
Re: C and the GNU assembler: how to deal with structs?
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.~ wrote:You start modifying in assembly and end up in C.
Cheers,
bzt