Skip to content

libc: the snprintf of libSceLibcInternal.sprx prints %p as bare lowercase hex without 0x (PS5 payload process); FormatPointer prints the FreeBSD 0x form #7549

Closed

Description

@ionut27001

The snprintf of libSceLibcInternal.sprx prints %p as bare lowercase hex, without 0x (measured from a PS5 payload process)

Main formats it as FreeBSD does. LibcDetail::FormatPointer follows FreeBSD (44f00e2, "format %p as FreeBSD does on Windows", from #3111 and #3393). It prints 0x and the hex digits, so a null pointer comes out as 0x0. That commit replaced the host CRT's zero-padded 16-digit form on Windows, and #3111 expected the FreeBSD form to be what a title sees on the console. On firmware 12.70, snprintf from the system libSceLibcInternal.sprx, called from a payload process, prints neither form. The snprintf of a title's bundled libc.prx was not measured. FormatPointer is used by:

  • the narrow printf family on Windows;
  • the wide printf family on every host;
  • the narrow family on Linux only when the format has a wide argument or %m. Otherwise Linux hands the format to glibc, as the narrow printf entry in docs/dev/debt/libc.md says, and glibc prints 0x... or (nil). Open PR fix(libc): use guest pointer formatting on Linux #6483 adds p to the Linux selector, so after that PR a narrow %p would print what FormatPointer prints. Its selector still matches l only before s or c, so a narrow %lp without a wide argument would still go to glibc.

The console's libSceLibcInternal.sprx behaves differently. Called from a payload process on a PS5 (firmware 12.70), its snprintf prints %p as follows:

  • lowercase hex digits with no 0x, even with #;
  • 0 for a null pointer;
  • + and space are ignored;
  • a width pads with spaces on the left, - pads on the right, 0 pads with zeros, and a precision sets the minimum number of digits; %.0p of a null pointer prints nothing (0 together with a precision or with - was not measured);
  • the l length is accepted.

Its swprintf was checked only for %p, %10p, %-10p and %.8p, with the values 0, 0x1234 and 0xabcdef. For %p and %.8p it prints the same as snprintf. snprintf was not run with %10p or %-10p, but p23 ran it with %20p and %-20p, and swprintf pads the bare digits the same way.

Format Value snprintf of libSceLibcInternal.sprx FormatPointer on main
[%p] 0 [0] [0x0]
[%p] 0x1234 [1234] [0x1234]
[%5p] 0x1234 [ 1234] [0x1234]
[%-5p] 0x1234 [1234 ] [0x1234]
[%05p] 0x1234 [01234] [0x1234]
[%.8p] 0x1234 [00001234] [0x00001234]
[%10.6p] 0x1234 [ 001234] [ 0x001234]
[%.0p] 0 [] [0x]
[%#p] 0x1234 [1234] [0x1234]
[%+p] 0x1234 [1234] [0x1234]
[% p] 0x1234 [1234] [0x1234]
[%lp] 0x1234 [1234] throws Unsupported format conversion

The FormatPointer column was produced by calling LibcDetail::FormatPointer from main (922dc89) with these specs; the formatter code is the same on 5bf2e13, apart from scanf changes in Formatting.cpp. The %lp row comes from the formatters themselves: both take %p only with no length.

Fix shape: to match the snprintf of libSceLibcInternal.sprx as measured, FormatPointer would drop the 0x and count only the digits towards the width. The narrow formatter would also accept %lp. The console's swprintf was not run with %lp, so the wide formatter would keep throwing for it until that is measured. On Linux a narrow format reaches the guest formatter only when the selector routes it, so the selector would also need to route %p and %lp. The tests in Formatting.cpp and GuestWideFormatting.cpp would change with it, and so would #6483's expectations. Open PR #5303 also touches this area: its GuestWideFormatting.cpp test pins swprintf(u"%lp") as a failure.

I have not opened a PR because this reverses a deliberate choice on main and overlaps #6483 and #5303. Whether to follow libSceLibcInternal.sprx here is for the maintainers to decide.

Measured (the pNN: prefix names the probe; the rest of each line is copied unchanged):

p23: meta fw=0x12700001 pid=135 sdk=v0.44 compiler=Clang 18.1.8 built=Oct 18 2026 02:46:33
p43: meta fw=0x12700001 pid=158 sdk=v0.44 compiler=Clang 18.1.8 built=Oct 18 2026 04:44:46
p43: resolve sym=snprintf module=libSceLibcInternal.sprx exported=yes
p43: resolve sym=swprintf module=libSceLibcInternal.sprx exported=yes
p23: case snprintf fmt="%p" ret=1 text="0"
p23: case snprintf fmt="%#p" ret=4 text="1234"
p43: case pfmt fmt="[%p]" value=0 ret=3 text="[0]"
p43: case pfmt fmt="[%p]" value=0x1234 ret=6 text="[1234]"
p43: case pfmt fmt="[%p]" value=0xffffffffffffffff ret=18 text="[ffffffffffffffff]"
p43: case pfmt fmt="[%5p]" value=0x1234 ret=7 text="[ 1234]"
p43: case pfmt fmt="[%-5p]" value=0x1234 ret=7 text="[1234 ]"
p43: case pfmt fmt="[%05p]" value=0x1234 ret=7 text="[01234]"
p43: case pfmt fmt="[%.8p]" value=0x1234 ret=10 text="[00001234]"
p43: case pfmt fmt="[%10.6p]" value=0x1234 ret=12 text="[    001234]"
p43: case pfmt fmt="[%.0p]" value=0 ret=2 text="[]"
p43: case pfmt fmt="[%#p]" value=0x1234 ret=6 text="[1234]"
p43: case pfmt fmt="[%+p]" value=0x1234 ret=6 text="[1234]"
p43: case pfmt fmt="[% p]" value=0x1234 ret=6 text="[1234]"
p43: case pfmt fmt="[%lp]" value=0x1234 ret=6 text="[1234]"
p23: case snprintf fmt="%20p|" ret=21 text="                1234|"
p23: case snprintf fmt="%-20p|" ret=21 text="1234                |"
p43: case wpfmt fmt="[%p]" value=0x1234 ret=6 text="[1234]"
p43: case wpfmt fmt="[%10p]" value=0x1234 ret=12 text="[      1234]"
p43: case wpfmt fmt="[%-10p]" value=0x1234 ret=12 text="[1234      ]"
p43: case wpfmt fmt="[%.8p]" value=0x1234 ret=10 text="[00001234]"

Method:

  • Setup: the console runs firmware 12.70 and is jailbroken.
  • Probes: ELF payloads built with ps5-payload-dev/sdk v0.44 and clang 18.1.8, started through a payload manager, so they ran in a payload process, not in a title.
  • Calls: snprintf and swprintf were resolved from libSceLibcInternal.sprx and called through pointers.
  • Attachment: sources and the full logs are attached: printf-p-probes.zip.

AI-assisted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions