Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HLE_OS: Manually handle printfs from emulated software to prevent emulated software from crashing Dolphin with an invalid printf formatting string. #11025

Merged
merged 2 commits into from Aug 19, 2023

Conversation

AdmiralCurtiss
Copy link
Contributor

@AdmiralCurtiss AdmiralCurtiss commented Aug 31, 2022

This is incomplete and not tested, I just was reminded that I had this. I may or may not come back to this, someone else is free to take this over if they want...

For reference, I'm unsure of the exact circumstances, but I have seen Dolphin crash from a game invoking a HLE-hooked printf(), which is what triggered me going down this path.

@sepalani
Copy link
Contributor

Just wondering, can't fmt::sprintf be used and the format string parsing would only be done to grab the required arguments and store them into a fmt::dynamic_format_arg_store? I might give it a shot when I have the time.

@AdmiralCurtiss
Copy link
Contributor Author

AdmiralCurtiss commented Aug 31, 2022

I think that still results in platform inconsistency (consider eg. platforms where long is 64 bit vs platforms where long is 32 bit), but feel free to give it a shot!

e: Also you have to filter out anything the hooked function wouldn't support, like positional arguments...

@dreamsyntax
Copy link
Member

dreamsyntax commented Apr 4, 2023

Shadow the Hedgehog - [GUPE8P, GUPP8P, GUPJ8P]
Would have a large reduction in Dolphin crashing to desktop (CTD) from this PR.
I have personally confirmed this PR affecting this game with a reproducible state I made where after ~10 seconds in a position a print triggers.
Because Dolphin currently defaults to Console Type : Development HW3 (10000006), this game's print functions run.

In this game OSReport EXI shows human readable strings, and OSReport HLE shows tons of junk every frame.
Occasionally, depending on the print Dolphin can CTD, below was one example.
image

800ae6c0->800ae6ac| �R�怙エV閨8�_'リ

I confirmed after testing this build the crash to desktop is resolved, though there is a brief hiccup/stutter when it reaches the problematic log. The game continues emulation successfully.

do
{
if (!HostIsRAMAddress(guard, address) || !HostIsRAMAddress(guard, address + 1))
break;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this return error instead of "partial" string?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly... but if yes so should HostGetString().

@AdmiralCurtiss AdmiralCurtiss changed the title [WIP] Manual printf handling in HLE_OS::GetStringVA() for consistent behavior between platforms and memory access safety. HLE_OS: Manually handle printfs from emulated software to prevent emulated software from crashing Dolphin with an invalid printf formatting string. Jul 17, 2023
@AdmiralCurtiss AdmiralCurtiss marked this pull request as ready for review July 17, 2023 20:33
@AdmiralCurtiss
Copy link
Contributor Author

This has been sitting here long enough. I think it works, even if it's a bit ugly in spots, and it's definitely better than letting emulated software crash or worse write to arbitrary memory with a rogue printf statement.

Copy link
Contributor

@sepalani sepalani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll review the rest of the PR, asap. I'm going to test it with a bunch of games I have which are displaying debug messages.

Comment on lines 877 to 880
u16 res = HostRead_U16(guard, address);
if (!res)
break;
s += static_cast<u16>(res);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't res be const'd and is the static_cast necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, that's supposed to cast to char16_t.

Comment on lines 215 to 220
HLEPrintArgsVAList(const Core::CPUThreadGuard& guard, HLE::SystemVABI::VAList& va_list)
: m_guard(guard), m_va_list(va_list)
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't HLE::SystemVABI::VAList& va_list be passed as a pointer in case it's modified according to Dolphin's coding style? If the coding style has changed or allows some exceptions, they might be worth mentioning in the Contributing.md file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mhm, I think that's actually ambiguous, since we're not actually assigning a new value to the passed va_list, but yeah sure it doesn't hurt.

@AdmiralCurtiss AdmiralCurtiss force-pushed the hle-printf branch 2 times, most recently from 326f81a to 86f9846 Compare July 22, 2023 13:14
Copy link
Contributor

@sepalani sepalani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some more remarks.

I'm wondering, can't the remaining StringFromFormat be replaced with fmt::sprintf?

Source/Core/Core/HLE/HLE_OS.cpp Outdated Show resolved Hide resolved
Source/Core/Core/HLE/HLE_OS.cpp Outdated Show resolved Hide resolved
Source/Core/Core/HLE/HLE_OS.cpp Outdated Show resolved Hide resolved
Source/Core/Core/HLE/HLE_OS.cpp Outdated Show resolved Hide resolved
Comment on lines 386 to 380
if (i >= string.size())
{
// not a valid formatting string, print the formatting string as-is
result += string.substr(formatting_start_position);
break;

ArgumentBuffer += string[i];
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't you returning a partially formatted string instead of the original format string string? You're also skipping all the subsequent conversion specifications. I'm unsure of the behaviour on actual hardware/game/homebrew. I'll write some tests to see how they behave.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well this is undefined behavior, it's reading past the end of the string. This is basically if you had something like eg. printf("text %s %0", "something", 5);; that last %0 has a padding_zeroes_flag but then nothing else after it. I'd argue printing text something %0 in this case is more useful than printing text %s %0 but ymmv, we can technically do whatever.

Source/Core/Core/HLE/HLE_OS.cpp Outdated Show resolved Hide resolved
@AdmiralCurtiss AdmiralCurtiss force-pushed the hle-printf branch 3 times, most recently from 9e66d5f to 5bfc360 Compare July 29, 2023 14:12
@AdmiralCurtiss
Copy link
Contributor Author

I've also changed the StringFromFormat to fmt::sprintf. Sadly there's no fmt::sprintf_to but whatever, not like this is particularly performance-critical...

Copy link
Contributor

@sepalani sepalani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise, this PR seems to work fine with the game I tested.

{
fmt::format_to(std::back_inserter(result),
fmt::runtime(left_justified_flag ? "{0:<{1}}" : "{0:>{1}}"),
static_cast<unsigned char>(value), field_width.value_or(0));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After some testing, I had a regression. Casting the value as signed or unsigned char will display it as an integer. This should be casted as plain char instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah so just static_cast<char>(value)? Weird that that makes a difference.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was surprised too, apparently it was done to fix uint8_t and int8_t being displayed as character instead of integer.

…lated software from crashing Dolphin with an invalid printf formatting string.
@AdmiralCurtiss
Copy link
Contributor Author

@sepalani Any further comments?

Copy link
Contributor

@sepalani sepalani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. This PR is also better than the current way we're logging things. @dreamsyntax might want to double check that it still fixes the crashes on Shadow the Hedgehog.

Sadly, I'm unable to reproduce the crash mentioned by @dreamsyntax on Shadow the Hedgehog (PAL), with either this PR or master. Both tried to print nonsense still. There is no known symbol maps for this game, AFAIK, so Dolphin autodetection creates false positives.

I noticed one nlPrintf function at 8001c574 which doesn't seem log related but might use varargs. I was unable to figure out if its parameters point to valid strings.

Whereas, there is another function detected as ___blank at 8000d374 which might or might not be log related. However, it might be related to the boost library (v1.32, since the string occurrences don't exist prior to this version) and shared_ptr serialization. The first argument (r3) passed to this function when dereferenced several times points to r6 then a boost string. The r6 and r3 values are set right before the ___blank function call so there is a very high chance for them to be related.

Some examples (no surprise that dolphin autodection failed to find the string):

  • r3=8060c534 -> 807f07a0 -> r6=80560298 -> 805ee9a0 (char**) -> 80512824 (char*)
    • string at 80512824: boost::detail::sp_counted_base_impl<Vibration::ManagerUnit *, boost::checked_deleter<Vibration::ManagerUnit>>
  • r3=8060c4f4 -> 807f0818 -> r6=8054ddac -> 805eba3c (char**) -> 804f7a10 (char*)
    • string at 804f7a10: boost::detail::sp_counted_base_impl<SonicteamUSA::System::Camera::CameraScreen *, boost::checked_deleter<SonicteamUSA::System::Camera::CameraScreen>>

@AdmiralCurtiss AdmiralCurtiss merged commit f19651e into dolphin-emu:master Aug 19, 2023
11 checks passed
@AdmiralCurtiss AdmiralCurtiss deleted the hle-printf branch August 19, 2023 23:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
5 participants