[HN Gopher] Motorola 68k Application Binary Interface (ABI)
       ___________________________________________________________________
        
       Motorola 68k Application Binary Interface (ABI)
        
       Author : mshockwave
       Score  : 55 points
       Date   : 2022-01-20 12:24 UTC (10 hours ago)
        
 (HTM) web link (m680x0.github.io)
 (TXT) w3m dump (m680x0.github.io)
        
       | cmrdporcupine wrote:
       | It's great to see the 68k backends of GCC and LLVM getting love
       | again.
       | 
       | In many respects the 68000 is the oldest most "retro"
       | architecture for which you can still run a modern compiler suite.
       | Processor introduced in 1979 for which you could compile a Rust
       | binary today. That's pretty amazing.
        
         | jagrsw wrote:
         | The ISA is really beautiful (maybe with the exception of BCD
         | additions). I have a m68k disk image/kernel which works with
         | qemu-system-m68k, out of historical sentiment (used to have
         | A500, now I have A4000 - sadly no time to play with it, so in
         | my basement).
         | 
         | As for abi/syscalls. I noticed that strace reports hundreds of
         | calls to get_thread_area() under Linux/m68k. I presume under
         | x86, arm etc. some CPU register is used to store pointer to the
         | TLS, but under m68k "they" decided to invoke syscall every
         | single time. I don't think it'd be very efficient, and also it
         | pollutes strace output, unfortunately, given how frequently TLS
         | is accessed these days.                 $ uname -a       Linux
         | ds-m68k 5.16.0-rc1-g02e0dceb9e3b #7 Fri Jan 14 01:27:22 CET
         | 2022 m68k GNU/Linux       $ strace -c /bin/ls       % time
         | seconds  usecs/call     calls    errors syscall       ----
         | ----------- ----------- --------- --------- ----------------
         | 0.00    0.000000           0         6           read
         | 0.00    0.000000           0         1           write
         | 0.00    0.000000           0        10           close
         | 0.00    0.000000           0         1           execve
         | 0.00    0.000000           0         2         2 access
         | 0.00    0.000000           0         3           brk       0.00
         | 0.000000           0         3           ioctl       0.00
         | 0.000000           0         1           munmap       0.00
         | 0.000000           0         2         2 statfs       0.00
         | 0.000000           0         1           uname       0.00
         | 0.000000           0        10           mprotect       0.00
         | 0.000000           0        14           mmap2       0.00
         | 0.000000           0         2           getdents64       0.00
         | 0.000000           0         8           openat       0.00
         | 0.000000           0       285           get_thread_area
         | 0.00    0.000000           0         1
         | set_thread_area       0.00    0.000000           0         9
         | statx       ---- ----------- ----------- --------- ---------
         | ----------------       100.00    0.000000           0       359
         | 4 total
        
           | pcwalton wrote:
           | I wouldn't say anything with separate register files for
           | addresses and data is "beautiful", but it's certainly miles
           | better than other microprocessors at the same time like the
           | 65816.
        
           | Findecanor wrote:
           | I have been playing with defining my own toy ABI, and I came
           | up with a trick for thread-local storage:
           | 
           | Allocate the segment for TLS and the thread stack together so
           | that both start (stack grows down, TLS up) at a 2*n - aligned
           | address, with n large enough so that the stack size never
           | exceeds 2*n. Then, you could always get TLS-1 from SP by
           | setting the lower bits to 1.
           | 
           | To avoid an odd size for the segment, you could put the TLS
           | block at negative offset from the end of the segment.
           | 
           | I had intended mine for machines with 64-bit pointers, but
           | data structures tend to be smaller on 32-bit machines, so
           | they can cope with smaller stacks. On the M68K Amiga the
           | stack size was fixed and often relatively small: recommended
           | minimum was 16000 bytes, and for large programs recommended
           | size was 80000. Modern operating systems for CPUs with MMUs
           | grow the stack dynamically but still have to set aside a part
           | of the address space for the maximum it could grow into.
        
           | jandrese wrote:
           | Maybe that is a form of exploit mitigation? Maybe they can't
           | cache the TLS pointer because the kernel may change it out
           | from under you without warning? I do agree that it smells
           | like a possible performance issue and it seems like there
           | must be a better way to do it.
        
             | jagrsw wrote:
             | From
             | https://lists.debian.org/debian-68k/2007/11/msg00071.html
             | (Draft TLS/NPTL ABI for m68k and ColdFire, version 0.2)
             | There are no spare registers available to designate as the
             | thread       register.  Therefore, kernel magic is needed
             | to obtain the thread       pointer from userspace.
             | 
             | I guess there was unused 'fs' in intel. There was nothing
             | left in m68k.
        
               | jandrese wrote:
               | This doesn't really answer the question. Even if there
               | isn't a free register the value could be stored on the
               | stack and avoid the syscall overhead. Or maybe I'm
               | misunderstanding what that variable is doing. Or at the
               | very least the value could be calculated once via the
               | kernel and then cached in main memory/a general purpose
               | register.
        
               | jagrsw wrote:
               | I'm speculating heavily here, but it seems TLS seems to
               | be userland thing, but with OS support (e.g. CLONE_SETTLS
               | flags, *_thread_area()).
               | 
               | Maybe it was thought more like a thread-level thing, and
               | not runtime thing. E.g. if we had some competing runtimes
               | in one process space (e.g. go, jvm, libc), then they can
               | all obtain TLS pointer via some unified method (via
               | register or syscall). Otherwise there would be need for
               | some chosen runtime, which would keep the pointer and
               | distribute it around - which would create another set of
               | problems.
               | 
               | Putting it on the top of initial process stack would work
               | I guess, though.
        
               | johntb86 wrote:
               | The value is per thread, so if you're caching it to main
               | memory (including in the stack) then you need one copy
               | per thread and you need to somehow know which one to use
               | based on what thread is running. You'd need to store that
               | index somewhere so there'd be an infinite recursion. Even
               | if it's stored at the base of the current stack you don't
               | want to have to walk all the stack frames to find it.
               | 
               | You can store it in a GPR instead (that's what some
               | modern architectures do) but then it needs to be part of
               | the ABI. Otherwise at the beginning of a function you
               | can't assume any register contains the correct value.
        
           | windenntw wrote:
           | Could you post a bit more info about that binary? (eg: run
           | ldd to dump the used libs, etc)
           | 
           | Could you get some kind of stack trace at the time of the
           | get_thread_area call?
           | 
           | I'd like to be able to pinpoint the call(s) somewhere in
           | 
           | https://codesearch.debian.net/search?q=get_thread_area&perpk.
           | ..
        
             | jagrsw wrote:
             | # ldd /bin/ls      libselinux.so.1 => /lib/m68k-linux-
             | gnu/libselinux.so.1 (0xc0028000)      libc.so.6 =>
             | /lib/m68k-linux-gnu/libc.so.6 (0xc004e000)
             | /lib/ld.so.1 (0xc0000000)      libpcre2-8.so.0 =>
             | /lib/m68k-linux-gnu/libpcre2-8.so.0 (0xc019e000)
             | libdl.so.2 => /lib/m68k-linux-gnu/libdl.so.2 (0xc01f9000)
             | 
             | Both from ld and from libc. HTH                 # strace -o
             | /dev/fd/1 -e get_thread_area -i /bin/ls | cut -f2 -d"[" |
             | cut -f1 -d"]" | sort | uniq       ????????       c0018330
             | c007313c            # gdb /bin/ls       (gdb) break
             | *0xc0018330       (gdb) r       Breakpoint 1,
             | __m68k_read_tp () at ../sysdeps/unix/sysv/linux/m68k/m68k-
             | helpers.c:24       (gdb) bt       #0  __m68k_read_tp () at
             | ../sysdeps/unix/sysv/linux/m68k/m68k-helpers.c:24       #1
             | 0xc0002e76 in init_tls (naudit=0) at rtld.c:808       #2
             | 0xc0005318 in dl_main (phdr=<optimized out>,
             | phnum=<optimized out>, user_entry=<optimized out>,
             | auxv=<optimized out>)         at rtld.c:2030       #3
             | 0xc00152c0 in _dl_sysdep_start (start_argptr=0xeffffd20,
             | dl_main=0xc0003704 <dl_main>) at ../elf/dl-sysdep.c:250
             | #4  0xc0003194 in _dl_start_final (arg=0xeffffd20,
             | info=0xeffffabe) at rtld.c:489       #5  0xc000348e in
             | _dl_start (arg=0xeffffd20) at rtld.c:584       #6
             | 0xc000289c in _start () from /lib/ld.so.1
             | (gdb) continue        Breakpoint 1, __m68k_read_tp () at
             | ../sysdeps/unix/sysv/linux/m68k/m68k-helpers.c:24       24
             | in ../sysdeps/unix/sysv/linux/m68k/m68k-helpers.c
             | (gdb) bt       #0  __m68k_read_tp () at
             | ../sysdeps/unix/sysv/linux/m68k/m68k-helpers.c:24       #1
             | 0xc000d83e in _dl_fixup (save_a0=-1073325311,
             | save_a1=-1072331548, l=0xc0023a90, reloc_arg=96) at dl-
             | runtime.c:99       #2  0xc00132ca in _dl_runtime_resolve ()
             | at ../sysdeps/m68k/dl-trampoline.S:43            (gdb)
             | delete breakpoints        (gdb) break *0xc007313c
             | (gdb) continue        Breakpoint 2, __m68k_read_tp () at
             | ../sysdeps/unix/sysv/linux/m68k/m68k-helpers.c:24       24
             | ../sysdeps/unix/sysv/linux/m68k/m68k-helpers.c: No such
             | file or directory.       (gdb) bt       #0  __m68k_read_tp
             | () at ../sysdeps/unix/sysv/linux/m68k/m68k-helpers.c:24
             | #1  0xc0110ce4 in statfs () at ../sysdeps/unix/syscall-
             | template.S:123       #2  0xc002ffd8 in ?? () from
             | /lib/m68k-linux-gnu/libselinux.so.1       #3  0xc0047489 in
             | ?? () from /lib/m68k-linux-gnu/libselinux.so.1       #4
             | 0xeffffbe0 in ?? ()       #5  0xc004b328 in ?? () from
             | /lib/m68k-linux-gnu/libselinux.so.1       #6  0x00000001 in
             | ?? ()       #7  0x8001e31c in stderr ()       #8
             | 0xc004b480 in ?? () from /lib/m68k-linux-
             | gnu/libselinux.so.1       #9  0x00000000 in ?? ()
             | (gdb) continue        Breakpoint 2, __m68k_read_tp () at
             | ../sysdeps/unix/sysv/linux/m68k/m68k-helpers.c:24       24
             | in ../sysdeps/unix/sysv/linux/m68k/m68k-helpers.c
             | (gdb) bt       #0  __m68k_read_tp () at
             | ../sysdeps/unix/sysv/linux/m68k/m68k-helpers.c:24       #1
             | 0xc0073408 in __GI___errno_location () at errno-loc.c:26
             | #2  0xc002ffe4 in ?? () from /lib/m68k-linux-
             | gnu/libselinux.so.1       #3  0xc004b328 in ?? () from
             | /lib/m68k-linux-gnu/libselinux.so.1       #4  0x00000001 in
             | ?? ()       #5  0x8001e31c in stderr ()       #6
             | 0xc004b480 in ?? () from /lib/m68k-linux-
             | gnu/libselinux.so.1       #7  0x00000000 in ?? ()
             | (gdb) continue        Breakpoint 2, __m68k_read_tp () at
             | ../sysdeps/unix/sysv/linux/m68k/m68k-helpers.c:24       24
             | in ../sysdeps/unix/sysv/linux/m68k/m68k-helpers.c
             | (gdb) bt       #0  __m68k_read_tp () at
             | ../sysdeps/unix/sysv/linux/m68k/m68k-helpers.c:24       #1
             | 0xc00c1528 in ptmalloc_init () at arena.c:358       #2
             | 0xc00c4786 in ptmalloc_init () at arena.c:326       #3
             | malloc_hook_ini (sz=304, caller=0xc00b00ec
             | <__fopen_internal+22>) at hooks.c:31       #4  0xc00c461c
             | in __GI___libc_malloc (bytes=304) at malloc.c:3203       #5
             | 0xc00b00ec in __fopen_internal (filename=0xc004746d
             | "/proc/filesystems", mode=0xc0047562 "re", is32=1) at
             | iofopen.c:58       #6  0xc00b01c2 in _IO_new_fopen
             | (filename=0xc004746d "/proc/filesystems", mode=0xc0047562
             | "re") at iofopen.c:86       #7  0xc0036c06 in
             | selinuxfs_exists () from /lib/m68k-linux-
             | gnu/libselinux.so.1                 (gdb) disassemble
             | __m68k_read_tp       Dump of assembler code for function
             | __m68k_read_tp:          0xc0018328 <+0>: movel #333,%d0
             | 0xc001832e <+6>: trap #0       => 0xc0018330 <+8>: moveal
             | %d0,%a0          0xc0018332 <+10>: rts
             | (gdb) info proc mappings        process 336       Mapped
             | address spaces:           Start Addr   End Addr       Size
             | Offset objfile      0x80000000 0x8001b000    0x1b000
             | 0x0 /usr/bin/ls      0x8001d000 0x8001e000     0x1000
             | 0x1b000 /usr/bin/ls      0x8001e000 0x8001f000     0x1000
             | 0x1c000 /usr/bin/ls      0x8001f000 0x80020000     0x1000
             | 0x0 [heap]      0xc0000000 0xc0020000    0x20000        0x0
             | /usr/lib/m68k-linux-gnu/ld-2.33.so      0xc0021000
             | 0xc0022000     0x1000    0x1f000 /usr/lib/m68k-linux-
             | gnu/ld-2.33.so      0xc0022000 0xc0024000     0x2000
             | 0x20000 /usr/lib/m68k-linux-gnu/ld-2.33.so      0xc0028000
             | 0xc0049000    0x21000        0x0 /usr/lib/m68k-linux-
             | gnu/libselinux.so.1      0xc0049000 0xc004b000     0x2000
             | 0x21000 /usr/lib/m68k-linux-gnu/libselinux.so.1
             | 0xc004b000 0xc004c000     0x1000    0x21000
             | /usr/lib/m68k-linux-gnu/libselinux.so.1      0xc004c000
             | 0xc004d000     0x1000    0x22000 /usr/lib/m68k-linux-
             | gnu/libselinux.so.1      0xc004d000 0xc004e000     0x1000
             | 0x0       0xc004e000 0xc018e000   0x140000        0x0
             | /usr/lib/m68k-linux-gnu/libc-2.33.so      0xc018e000
             | 0xc0190000     0x2000   0x140000 /usr/lib/m68k-linux-
             | gnu/libc-2.33.so      0xc0190000 0xc0192000     0x2000
             | 0x140000 /usr/lib/m68k-linux-gnu/libc-2.33.so
             | 0xc0192000 0xc0196000     0x4000   0x142000
             | /usr/lib/m68k-linux-gnu/libc-2.33.so      0xc0196000
             | 0xc019e000     0x8000        0x0       0xc019e000
             | 0xc01f3000    0x55000        0x0 /usr/lib/m68k-linux-
             | gnu/libpcre2-8.so.0.10.4      0xc01f3000 0xc01f5000
             | 0x2000    0x55000 /usr/lib/m68k-linux-
             | gnu/libpcre2-8.so.0.10.4      0xc01f5000 0xc01f6000
             | 0x1000    0x55000 /usr/lib/m68k-linux-
             | gnu/libpcre2-8.so.0.10.4      0xc01f6000 0xc01f7000
             | 0x1000    0x56000 /usr/lib/m68k-linux-
             | gnu/libpcre2-8.so.0.10.4      0xc01f7000 0xc01f9000
             | 0x2000        0x0       0xc01f9000 0xc01fb000     0x2000
             | 0x0 /usr/lib/m68k-linux-gnu/libdl-2.33.so      0xc01fb000
             | 0xc01fe000     0x3000     0x2000 /usr/lib/m68k-linux-
             | gnu/libdl-2.33.so      0xc01fe000 0xc01ff000     0x1000
             | 0x3000 /usr/lib/m68k-linux-gnu/libdl-2.33.so
             | 0xc01ff000 0xc0200000     0x1000     0x4000
             | /usr/lib/m68k-linux-gnu/libdl-2.33.so      0xeffdf000
             | 0xf0000000    0x21000        0x0 [stack]
        
               | Taniwha wrote:
               | errno and malloc() not that surpsising
        
               | windenntw wrote:
               | excellent, thanks!
               | 
               | so the traced calls would more or less be:
               | 
               | https://sources.debian.org/src/glibc/2.33-3/sysdeps/m68k/
               | tls...
               | 
               | https://sources.debian.org/src/glibc/2.33-3/sysdeps/unix/
               | sys...
               | 
               | Maybe it is all related to errno needing to read the base
               | TLS pointer? https://sources.debian.org/src/linux/5.15.15
               | -1/arch/m68k/ker...
               | 
               | Sounds like we'd benefit from placing the result of
               | calling sys_get_thread_area(void) in the VDSO area and
               | updating it from http://lxr.linux.no/linux+v5.14/arch/m68
               | k/include/asm/entry.... every time we do a process switch
        
               | jagrsw wrote:
               | > Maybe it is all related to errno needing to read the
               | base TLS pointer?
               | 
               | Probably everything that uses __thread requires this.
               | e.g. malloc(), or some functions that return pointers to
               | static buffers, and implementators wanted to make them
               | multi-thread safe.                 /*          The
               | interface of this function is completely stupid,
               | it requires a static buffer.  We relax this a bit in
               | that we allow one buffer for each thread.       */
               | static __thread char buffer[18];            char *
               | inet_ntoa (struct in_addr in)       {         unsigned
               | char *bytes = (unsigned char *) &in;         __snprintf
               | (buffer, sizeof (buffer), "%d.%d.%d.%d",
               | bytes[0], bytes[1], bytes[2], bytes[3]);         return
               | buffer;       }
        
               | jagrsw wrote:
               | Well.. I just noticed that implementation of inet_ntoa in
               | glibc is a bit suspect. Using %d with char, without
               | typecasting to (int) or using %hhd is tricky, and
               | depending on memory alignment rules, and on whether
               | arguments are passed in registers on on the stack this
               | can produce incorrect results.
               | 
               | This is because arguments from the stack can be taken as
               | ints (typically in batches of 4 octects), and put there
               | as char (1 octet typically, but alignment might force it
               | to be aligned to 4 bytes, I guess it depends).
               | Interesting.
        
       | [deleted]
        
       | mrlonglong wrote:
       | It would be nice to see the m68k get some 64 bit love to bring it
       | right up to date. I've been considering what such a beast could
       | look like compared to the cruddy x86 64 bit ISA.
        
         | cmrdporcupine wrote:
         | The moment you start doing that you are going to lose backwards
         | compatibility anyways, so may as well go back to the drawing
         | board and revisit other compromises made in the ISA (like
         | separate data and address registers, for example. Or big endian
         | instead of little endian) and you end up with something else,
         | IMHO.
         | 
         | Maybe something closer to a 64-bit version of the NS32k ISA.
        
       | herio wrote:
       | The AmigaOS ABI was really designed for the m68k, it leverages
       | registers as much as possible. Pass variables in data registers,
       | pointers in address registers. A4 for local data, A6 for the
       | library pointer, results in D0.
       | 
       | That said, most library routines would then store the register
       | contents on the stack and restore on return so not sure it's more
       | efficient for memory access etc.
       | 
       | The m68k is the most CISC of ISAs I've seen, you have plentiful
       | addressing modes and register orthogonality. It had adressing
       | modes to do auto postincrements/predecrements, indirect indexed
       | accesses and all sorts of fun.
        
       | gmueckl wrote:
       | The 68k series are rather simple designs by today's standard and
       | should be old enough to have lost patent protection. How hard
       | would it be to reimplement the ISA as a Verilog/VHDL softcore?
        
         | gpderetta wrote:
         | It has been done. Search for the Apollo 68080 for example.
         | 
         | edit: also relevant is the the PiStorm, a software emulated
         | 680x0 running on a RaspberryPi _that plugs into a 680x0
         | socket_.
         | 
         | edit2: the MiST/MiSTer are also well known 680x0 open source
         | FPGA implementations.
        
       ___________________________________________________________________
       (page generated 2022-01-20 23:02 UTC)