Discussion:
x64 address indexed by 32-bit address
Add Reply
Paul Edwards
2024-01-19 16:00:38 UTC
Reply
Permalink
(I tried posting this elsewhere but I'm
not sure whether it got out - I possibly
misused Thunderbird)


Hi.

I am using a slightly modified GCC 3.2.3
to generate x64 code, and using PDPCLIB
and running under Linux x64.

I don't trust either (my) PDPCLIB or my
modified GCC 3.2.3

Assuming PDPCLIB is behaving correctly,
it is showing that the stack is above
4 GiB. It seems to be a 48-bit address.

The executable appears to be loaded in
low memory - way below 2 GiB.

***@kerravon-pc:~/scratch/eee/pdos/pdpclib$ ./pdptest ab def
welcome to pdptest
main function is at 00401334
allocating 10 bytes
m1 is 004087A8
allocating 20 bytes
m2 is 00408828
stack is around 7FFC36A1053C
printing arguments
argc = 3
arg 0 is <./pdptest>
arg 1 is <ab>
arg 2 is <def>
***@kerravon-pc:~/scratch/eee/pdos/pdpclib$


Now I have this code:

printf("argv[0] is %p %s\n", argv[0], argv[0]);
printf("len is %d\n", (int)strlen(argv[0]));
p = argv[0] + strlen (argv[0]);
printf("p is %p\n", p);
printf("p as string is %s\n", p);
printf("p current is %x\n", p[0]);
printf("as negative is %x\n", p[-1]);


which is generating (for that last line):

LM1873:
movl $4294967295, %eax
addq -64(%rbp), %rax
movsbl (%rax),%esi
movl $LC445, %edi
movb $0, %al
call _printf

That first instruction - the movl - has
negative 1 as an unsigned value. I tried
manually changing the generated assembler
to $-1 but the result appears to be the
same (I may have stuffed up the test).

And it crashes:

***@kerravon-pc:~/scratch/eee/gcc/gcc$ ./gcc-new.exe
argv[0] is 7FFC8DC50294 ./gcc-new.exe
len is 13
p is 7FFC8DC502A1
p as string is
p current is 0
Segmentation fault (core dumped)
***@kerravon-pc:~/scratch/eee/gcc/gcc$


I suspect what is happening is that it is
adding the entire value of eax as an unsigned
32-bit value to the 64-bit address and so the
address is being offset by nearly 4 GiB
instead of being offset by 1 byte.

GCC 3.2.3 was used to build systems I believe,
so it "must" work in some circumstances.

Any idea how this was meant to work?

Thanks. Paul.
bart
2024-01-19 16:16:17 UTC
Reply
Permalink
Post by Paul Edwards
printf("argv[0] is %p %s\n", argv[0], argv[0]);
printf("len is %d\n", (int)strlen(argv[0]));
  p = argv[0] + strlen (argv[0]);
printf("p is %p\n", p);
printf("p as string is %s\n", p);
printf("p current is %x\n", p[0]);
printf("as negative is %x\n", p[-1]);
        movl    $4294967295, %eax
        addq    -64(%rbp), %rax
        movsbl  (%rax),%esi
        movl    $LC445, %edi
        movb    $0, %al
        call    _printf
That first instruction - the movl - has
negative 1 as an unsigned value. I tried
manually changing the generated assembler
to $-1 but the result appears to be the
same (I may have stuffed up the test).
Try changing:

movl $4294967295, %eax
to:
movq $-1, %rax

So changing both operands and the opcode.
Paul Edwards
2024-01-19 16:24:16 UTC
Reply
Permalink
Post by Paul Edwards
Post by Paul Edwards
movl $4294967295, %eax
addq -64(%rbp), %rax
movsbl (%rax),%esi
movl $LC445, %edi
movb $0, %al
call _printf
That first instruction - the movl - has
negative 1 as an unsigned value. I tried
manually changing the generated assembler
to $-1 but the result appears to be the
same (I may have stuffed up the test).
movl $4294967295, %eax
movq $-1, %rax
So changing both operands and the opcode.
Apologies - I forgot to spell that out.

Yes, I fully expect full 64-bit values to work.

What I would like to know is how it was possible
for GCC 3.2.3 to generate x64 operating systems
back in those days.

ie how did this code ever work?

Were operating systems back then restricted
via virtual memory to mask at the 4 GiB
mark to produce the required wrap or is there
something else I'm missing?

Thanks. Paul.
bart
2024-01-19 16:49:02 UTC
Reply
Permalink
Post by Paul Edwards
         movl    $4294967295, %eax
         addq    -64(%rbp), %rax
         movsbl  (%rax),%esi
         movl    $LC445, %edi
         movb    $0, %al
         call    _printf
That first instruction - the movl - has
negative 1 as an unsigned value. I tried
manually changing the generated assembler
to $-1 but the result appears to be the
same (I may have stuffed up the test).
          movl    $4294967295, %eax
          movq    $-1, %rax
So changing both operands and the opcode.
Apologies - I forgot to spell that out.
Yes, I fully expect full 64-bit values to work.
What I would like to know is how it was possible
for GCC 3.2.3 to generate x64 operating systems
back in those days.
ie how did this code ever work?
Bizarrely, it does work. I'm still trying to figure it out; I'll carry
on doing so.
bart
2024-01-19 18:40:34 UTC
Reply
Permalink
Post by bart
Post by Paul Edwards
         movl    $4294967295, %eax
         addq    -64(%rbp), %rax
         movsbl  (%rax),%esi
         movl    $LC445, %edi
         movb    $0, %al
         call    _printf
That first instruction - the movl - has
negative 1 as an unsigned value. I tried
manually changing the generated assembler
to $-1 but the result appears to be the
same (I may have stuffed up the test).
          movl    $4294967295, %eax
          movq    $-1, %rax
So changing both operands and the opcode.
Apologies - I forgot to spell that out.
Yes, I fully expect full 64-bit values to work.
What I would like to know is how it was possible
for GCC 3.2.3 to generate x64 operating systems
back in those days.
ie how did this code ever work?
Bizarrely, it does work. I'm still trying to figure it out; I'll carry
on doing so.
No, I made a mistake in my test assembly code. rax does get to a large
value.

Now, on x64, you can choose to use a 32-bit address mode so that the top
half of the address is ignored. But the bottom half will be
signed-extended, which I don't think will do it much good here.

Your (OP's) gcc code moreover uses a 64-bit address mode '(%rax)' not
'(%eax)'.

So it maybe it didn't work and was just a bug, that was fixed in a later
release.
David Brown
2024-01-19 16:55:49 UTC
Reply
Permalink
Post by Paul Edwards
         movl    $4294967295, %eax
         addq    -64(%rbp), %rax
         movsbl  (%rax),%esi
         movl    $LC445, %edi
         movb    $0, %al
         call    _printf
That first instruction - the movl - has
negative 1 as an unsigned value. I tried
manually changing the generated assembler
to $-1 but the result appears to be the
same (I may have stuffed up the test).
          movl    $4294967295, %eax
          movq    $-1, %rax
So changing both operands and the opcode.
Apologies - I forgot to spell that out.
Yes, I fully expect full 64-bit values to work.
What I would like to know is how it was possible
for GCC 3.2.3 to generate x64 operating systems
back in those days.
ie how did this code ever work?
Were operating systems back then restricted
via virtual memory to mask at the 4 GiB
mark to produce the required wrap or is there
something else I'm missing?
Thanks. Paul.
You've only posted bits of your code - try posting it all here. It's
quite possible that you've got undefined behaviour in your code, and
then you've no guarantees at all about what the compiler might do and
what might happen when you run it.

Also, why are you using such an ancient version of gcc? And what
changes did you make to it? If you want to test gcc versions and look
at the generated assembly, I recommend <https://godbolt.org> - though
the oldest gcc it has is 4.1.
Paul Edwards
2024-01-20 15:37:23 UTC
Reply
Permalink
Post by David Brown
Post by Paul Edwards
movl $4294967295, %eax
Thanks Scott (in other message) for
confirming that the generated code was bad.
That triggered me to make the compiler
have short == int == long == 64 bits to
overcome the problem and match SubC by
using the stack for parameters too.

It was to a large extent successful, but
not completely (floating point issues I
think), but it has triggered another
change of direction that I am still mulling
over. The ELF executable is available in
the UCX64 section of http://pdos.org
Post by David Brown
You've only posted bits of your code - try posting it all here. It's
It's gcc.c from gcc 3.2.3.
Post by David Brown
Also, why are you using such an ancient version of gcc?
It is sort of the only one known to support
the i370 target properly.

And I have spent 2 decades beating it into shape.

And the source code is (now) C90 compliant
as opposed to being written in Turtle Graphics
or whatever language modern gcc is now written in.

Note that it needs to be C90-compliant
otherwise it won't run on PDOS/386.
Post by David Brown
And what changes did you make to it?
Most of the work was in the i370 machine
definition, or other things in i370 to
support the EBCDIC target. It was Dave
Pitts who changed the body of gcc to
support EBCDIC. But that work would have
been obsoleted by gcc 3.4.6 which has the
ability to change the character set, but
gcc 3.4.6 has some internal errors when
targeting i370 and noone was willing/able
to fix them, so I abandoned my 3.4.6
effort and went back to 3.2.3.

But there were some intrusive changes, like
stopping after one error, since otherwise
it will scroll off the screen when run on
the primitive PDOS/386.

Note that it is i370 output that is accepted
on MVS 3.8j, not s390. Also what is accepted
by the S/370 hardware. And yes, there are
workarounds that sort of maybe could be used
with some effort sort of maybe.

And maybe one day I will change to use one
of those sort of maybe works methods. Or
more likely Jean-Marc will come through with
his SubC mods, and I'll then provide an i370
target.

Until then, it's gcc 3.2.3 that works at
all for the work that I am doing. And all
the targets that I support run on PDOS/386
except for this latest one which is 64-bit
and now that I have a reference - albeit
64-bit ELF - I may see if I can build it as
a 32-bit Windows executable to produce the
same output (and thus run on PDOS/386) or
I may make contact with Bart again to see
if his update for cc64 is available now
so that I can build a 64-bit Windows executable
and thus run on UCX64, as it is unlikely
that cc64 can handle the gcc 3.2.3 code,
even though it is C90-compliant. But it
shouldn't be necessary because I believe I
can target Win64 with this existing ELF
binary run under qemu x64 user, and have
stubs to change the calling convention for
the handful of kernel32 functions I need,
as has already been proven with 32-bit SubC.

BFN. Paul.
bart
2024-01-20 16:47:49 UTC
Reply
Permalink
Post by Paul Edwards
and thus run on UCX64, as it is unlikely
that cc64 can handle the gcc 3.2.3 code,
even though it is C90-compliant.
I wouldn't be able to build it anyway, with any compiler. It's one of
these hard-to-build jobs.

I've had a look at the sources, which include 7000 .c files and over
1000 .h files. But there is the usual configuration and makefile stuff
to navigate first, which will also general some of the header files needed.
Paul Edwards
2024-01-20 17:17:07 UTC
Reply
Permalink
Post by bart
Post by Paul Edwards
and thus run on UCX64, as it is unlikely
that cc64 can handle the gcc 3.2.3 code,
even though it is C90-compliant.
I wouldn't be able to build it anyway, with any compiler. It's one of
these hard-to-build jobs.
I've had a look at the sources, which include 7000 .c files and over
1000 .h files. But there is the usual configuration and makefile stuff
to navigate first, which will also general some of the header files needed.
Precisely why I have my own version of gcc 3.2.3
to simplify (in my mind at least) the process.

Less than 175 C source files, all contained in a
custom makefile suitable to be run on Windows
using pdmake - a windows executable.

Here's the one to build the ARM target (on Windows):

arm.mak:

# Produce a.out ARM executables for a PDOS-generic system
# Note that PDPCLIB must have been built using makefile.aga

CC=gccarm
COPTS=-msoft-float -fno-builtin -fno-common -O0 -D__UNOPT__ -mapcs-32 -S -D

all: clean gcc-new.exe

gcc-new.exe: \
alias.o \
attribs.o \
bb-reorder.o \
bitmap.o \
...
../libiberty/asprintf.o \
../libiberty/vasprintf.o \
../libiberty/getpagesize.o \
../libiberty/partition.o \
config/arm/arm.o \
unixio.o \
reg-stack.o \
doloop.o \
sdbout.o \
dbxout.o \
../libiberty/md5.o
ldarm -s -N -e ___crt0 -o gcc-new.exe ../../pdos/pdpclib/pgastart.o
temp.a ../../pdos/pdpclib/...

.c.o:
$(CC) $(COPTS) -o $*.s $<
asarm -o $@ $*.s
rm -f $*.s
ararm r temp.a $*.o
rm -f $*.o

clean:
rm -f *.s
rm -f *.o
rm -f temp.a
rm -f gcc-new.exe



Here are the different Windows compilers I support
to produce Windows targets (all 32-bit):

2023-12-04 05:19p 3,940 windows.mak
2021-08-07 02:46p 3,589 windowsb.mak
2023-11-26 02:59p 4,637 windowsm.mak
2021-08-07 02:11p 3,799 windowsw.mak

I can add cc64 to that list to produce a 64-bit
executable though. But it uses K&R syntax so I
think that is beyond scope.

However, I also have some support for GCC 3.4.6
which uses ANSI function declarations. So if you
want I can throw mm64 at it if you are interested
in updating mm64 for any issues found where mm64
is not C90-compliant.

BFN. Paul.
Scott Lurndal
2024-01-19 17:01:47 UTC
Reply
Permalink
Post by Paul Edwards
movl $4294967295, %eax
This will _not_ be sign extended,
Post by Paul Edwards
addq -64(%rbp), %rax
So instead of subtracting one from -64(%rbp),
it adds 4g. Bad generated code.
Paul Edwards
2024-02-13 06:13:21 UTC
Reply
Permalink
Post by Paul Edwards
movl $4294967295, %eax
addq -64(%rbp), %rax
movsbl (%rax),%esi
movl $LC445, %edi
movb $0, %al
call _printf
After an enormous amount of effort, I managed to
find out what was causing the issue.

My build process (a simple makefile) was not
including the x86-64.h that is present
in gcc 3.2.3 and included this crucial line:

#define SIZE_TYPE (TARGET_64BIT ? "long unsigned int" : "unsigned int")

So it thought that size_t was 32 bits instead
of 64 bits and was converting it to 32-bit
unsigned.

Once I activated that code, I get:

D:\devel\gcc\gcc>type foo.c
int foo(char *p)
{
return p[-1];
}

D:\devel\gcc\gcc>gccw64_l64 -O2 -S foo.c

D:\devel\gcc\gcc>type foo.s
.file "foo.c"
.text
.p2align 2,,3
.globl foo
foo:
.LFB1:
movsbl -1(%rcx),%eax
ret
.LFE1:

D:\devel\gcc\gcc>



Note that the use of rcx is instead of rdi is because
I added the Win64 ABI to gcc 3.2.3 (Win64 didn't exist
at the time).

That compiler is available as source (gcc-stage* in
custom.zip) and binary (customb.zip) at http://pdos.org
but relies on pdpcrt.dll (so that I can have 64-bit
long as opposed to msvcrt.dll) in \dos of pdos.vhd in
pdos.zip at the same place.

Makefile is windows.mak (shell/configure not required).

Code is C90-compliant (instead of being dependent
on posix).

BFN. Paul.

Loading...