Repository navigation
libc: the snprintf of libSceLibcInternal.sprx prints %p as bare lowercase hex without 0x (PS5 payload process); FormatPointer prints the FreeBSD 0x form #7549
Description
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 indocs/dev/debt/libc.mdsays, and glibc prints0x...or(nil). Open PR fix(libc): use guest pointer formatting on Linux #6483 addspto the Linux selector, so after that PR a narrow%pwould print whatFormatPointerprints. Its selector still matcheslonly beforesorc, so a narrow%lpwithout 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#; 0for a null pointer;+and space are ignored;- a width pads with spaces on the left,
-pads on the right,0pads with zeros, and a precision sets the minimum number of digits;%.0pof a null pointer prints nothing (0together with a precision or with-was not measured); - the
llength 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:
snprintfandswprintfwere resolved fromlibSceLibcInternal.sprxand called through pointers. - Attachment: sources and the full logs are attached: printf-p-probes.zip.
AI-assisted.
Activity
fix(libc): format %p the way the console's libc does