ps2dev / ps2sdk

Homebrew PS2 SDK
Other
954 stars 132 forks source link

crt0: fix clearing sbss/bss section #549

Closed rickgaiser closed 8 months ago

rickgaiser commented 8 months ago

@F0bes I'm not sure what was wrong with the assemble code, but argument passing somehow stopped working (in neutrino). I think I found a nicer solution to clearing the sbss and bss sections. Can you check if the issues you had are still fixed?

F0bes commented 8 months ago

This doesn't clear the sbss/bss section. You'd need to take a pointer to _end,_fbss like so: memset(&_fbss, 0, &_end - &_fbss);

I tried the above locally, and it still doesn't work however, I'm not entirely sure why that memset does not work.

I found this in an old commit. https://github.com/ps2dev/ps2sdk/commit/e9f7d9f209730a7d9ca6063571c1a7ab33b9ada8#diff-f2617db51cb5540d4098682e08fb7fdc8b38c290c5f3e876848b7d41587d03a7R47-R63

fjtrujy commented 8 months ago

I see, I was remembering that I was clearing that section. So I suppose, that if memset doesn’t work, you need to save arguments via ASM.

rickgaiser commented 8 months ago

This doesn't clear the sbss/bss section. You'd need to take a pointer to _end,_fbss like so: memset(&_fbss, 0, &_end - &_fbss);

Good catch. The memset seems to work for me with that change, becouse ps2link arguments are cleared again :(. Debugging what ps2link passes to neutrino I get the following:

_fbss @ 0x123480
_end  @ 0x12e9a8
argv  @ 0x123d34

So it looks like ps2link is passing the arguments inside the bss area.

F0bes commented 8 months ago

I was experiencing crashing after a soft reset when using the memset method. I assumed it was related to the newlib allocation global not being reset. We at least know the root cause of neutrinos arguments going missing though.

A quick online search didn't really give a conclusive answer as to where arguments are usually placed in memory. Maybe we need to add some calculation that gives of a range from argv->argvend and clear around that area. Or it might be possible to change the segment (or create a new segment in the linker script?) for the arguments.

A pretty easy way to test locally is to run the following code, soft reset, and run the following code again.

#include <stdio.h>
#include <kernel.h>

// gets put into bss
int global_var;

int main(void)
{
  printf("Global_var is %d\n",global_var);
  global_var = 1;
  SleepThread();
}

This should print 'Global_var is 0' twice.

rickgaiser commented 8 months ago

The bss section is used by our ELF executables, the data should always be 0 on entering main.

I think ps2link is doing some hacky things here. There's 2 ways to start an ELF:

  1. Start it normally using ps2 bios functions, this clears everything and passes arguments to the elf in a known good way. However this would also kill ps2link.
  2. Manually install all sections and start a thread, this allows ps2link to stay 'alive' while executing an elf. However the arguments cannot be passed using ps2 bios, so instead they are passed as argument to the thread.

This is why our crt0 has the if/else for arguments. To detect if they are passed by the bios, or by ps2link.

F0bes commented 8 months ago

So where does the bios place these arguments? Can't we have ps2link do that?

rickgaiser commented 8 months ago

So where does the bios place these arguments? Can't we have ps2link do that?

crt0 calls SetupThread. Perhaps ps2link can 'hook' into SetupThread to return the arguments properly.

Ziemas commented 8 months ago

I think traditionally (on normal OS's) the arguments get saved on the stack of the main thread. Can we do that somehow?

rickgaiser commented 8 months ago

I think traditionally (on normal OS's) the arguments get saved on the stack of the main thread. Can we do that somehow?

I don't think so. One of the first things crt0.o does is call SetupThread. This initializes the stack and returns the arguments. This is how it should work (using sony bios).

ps2link can't put the arguments on the stack before it is created.

Ziemas commented 8 months ago

Looks like on ExecPS2 the kernel copies the args into a buffer in kernel memory, then on SetupThread it copies this buffer into the user supplied arg buffer. So that's how the arguments survive a wipe of user memory in that case.

I guess we'd have to copy the args somewhere safe to guarantee keeping them around. Although why would they have been stored within reach of user bss anyway? Isn't ps2link typically hidden away somewhere.

rickgaiser commented 8 months ago

I would expect the ps2link arguments to be hidden below 1MiB, as defined here: https://github.com/ps2dev/ps2link/blob/master/ee/cmdHandler.c#L49

However that's not what what I see when debugging... I must say I'm also using an older ps2link as it's been broke for a long time.

I think a safe and compatible approach would be to copy the ps2link arguments to the area we already have reserved for arguments from the bios. This has to be done as 1 of the first steps, before clearing the bss area.

So regardless of what right/wrong place ps2link puts the arguments, we put them somewhere standard and safe, and clear bss.

Ziemas commented 8 months ago

Hm yeah, I was looking at ps2link in IDA for a bit and can't see how it could be affected by the BSS clearing.

F0bes commented 8 months ago

I believe @fjtrujy and I have narrowed it down to _kExecArg being located in the .bss segment. I've mentioned this is greater detail on discord.

This is a red herring and wasn't related ~~![image](https://github.com/ps2dev/ps2sdk/assets/29295048/c3302200-02b3-40c6-9226-570c2c195322)
Text Version Fobes: Does anyone know why we set _kExecArg like so: https://github.com/ps2dev/ps2sdk/blob/master/ee/kernel/src/tlbfunc.c#L57 Currently _kExecArg is being set between _fbss and _end, and it's getting cleared on elf load, which causes the lack of arguments. Fobes: Because the elf loader passes arguments to itself, and calls another elf before calling the target elf, the arguments are lost and the loader fails. Fobes: If we can find a way to get _kExecArg to a safer place than the .bss segment it would fix our problems. fjtrujy: Just one thing to clarify, currently even just having a single program that prints argv[0] is not working fine, as argv content is wiped, so it is no even needed to use the elf-loader to notice that things are right fjtrujy: Argv[0] should always contain the path of the itself executable Fobes: Also, _kExecArg is in the bss segment despite having that segment attribute. Unsure what is happening there. ``` mips64r5900el-ps2-elf-objdump -x hello.elf | grep kExec 00126fe0 g O .bss 00000004 _kExecArg ```
Ziemas commented 8 months ago

Can you guys test #555 and see if it fixes your issues?

rickgaiser commented 8 months ago

I can test later today, thanks.

Complicating things even further, there is also ps2-packer that can mess things up. I didn't understand why my arguments where not located at the place where I thought ps2link put them. Turns out it's becouse ps2-packer also does some if/else copy actions with it's own (different) crt0.o.: https://github.com/ps2dev/ps2-packer/blob/master/stub/crt0.s