mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
fix(libc): parse NaN payloads in strtod as measured on a PS5
In a payload process on a PS5 (firmware 12.70), the strtod and wcstod
of libSceLibcInternal.sprx keep the payload of a NAN(...) sequence,
read as FreeBSD 11's gdtoa hexnan reads it, under the quiet bit and
with the sign. strtof, wcstof, strtold and wcstold return the positive
default quiet NaN whatever the sign and the sequence, and consume a
sequence of letters, digits and underscores up to its closing
parenthesis. Here every NaN lost its payload, and strtof and strtold
kept the sign.
The model reproduces the 64 NaN inputs that p11, p14, p27 and p33
measured, including blanks and other characters inside the parentheses
and unterminated sequences, and the test now pins them. It also pins
two p71 cases measured through strtod: nan(1 2), and "nan( " followed
by NUL and ")". In hexnan, a blank after a payload digit, while a
32-bit word of the payload is still free, starts a blank skip that has
no NUL check, so when only blanks follow up to the terminating NUL the
console reads past it (p71); that case throws here. A blank right after
"nan(" is skipped with a NUL check, and the console stopped at the NUL
there.
AI-assisted: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
63e4a63804
commit
ae662c873d
4 files changed
+243
-42
No files matched your search
@@ -7,7 +7,9 @@
|
||||
#include <cwchar>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libc/include/SonyCrt.hpp"
|
||||
@@ -37,31 +39,130 @@ std::string AsciiPrefix(const char16_t* text) {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
bool IsNanSubject(const char* text) {
|
||||
struct NanSubject {
|
||||
const char* tail;
|
||||
bool negative;
|
||||
};
|
||||
|
||||
NanSubject FindNanSubject(const char* text) {
|
||||
while (*text == ' ' || (*text >= '\t' && *text <= '\r')) ++text;
|
||||
if (*text == '+' || *text == '-') ++text;
|
||||
return *text == 'n' || *text == 'N';
|
||||
bool negative = false;
|
||||
if (*text == '+' || *text == '-') negative = *text++ == '-';
|
||||
if ((text[0] | 0x20) == 'n' && (text[1] | 0x20) == 'a' && (text[2] | 0x20) == 'n') return {text + 3, negative};
|
||||
return {nullptr, negative};
|
||||
}
|
||||
|
||||
double QuietNan(double value, const char* text) {
|
||||
const auto bits = std::bit_cast<std::uint64_t>(value);
|
||||
if ((bits & 0x7ff0000000000000u) != 0x7ff0000000000000u || !IsNanSubject(text)) return value;
|
||||
return std::bit_cast<double>((bits & 0x8000000000000000u) | 0x7ff8000000000000u);
|
||||
unsigned HexNanDigit(unsigned char c) {
|
||||
if (c >= '0' && c <= '9') return c - '0' + 0x10;
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 0x1a;
|
||||
if (c >= 'A' && c <= 'F') return c - 'A' + 0x1a;
|
||||
return 0;
|
||||
}
|
||||
|
||||
float QuietNan(float value, const char* text) {
|
||||
const auto bits = std::bit_cast<std::uint32_t>(value);
|
||||
if ((bits & 0x7f800000u) != 0x7f800000u || !IsNanSubject(text)) return value;
|
||||
return std::bit_cast<float>((bits & 0x80000000u) | 0x7fc00000u);
|
||||
void ShiftNanWords(std::uint32_t* words, int from, int to, int digits) {
|
||||
const int shift = (8 - digits) * 4;
|
||||
const int carry = 32 - shift;
|
||||
int index = from;
|
||||
do {
|
||||
words[index] |= words[index + 1] << carry;
|
||||
words[index + 1] >>= shift;
|
||||
} while (++index < to);
|
||||
}
|
||||
|
||||
long double QuietNan(long double value, const char* text) {
|
||||
std::uint16_t exponent;
|
||||
std::memcpy(&exponent, reinterpret_cast<const unsigned char*>(&value) + sizeof(std::uint64_t), sizeof(exponent));
|
||||
if ((exponent & 0x7fffu) != 0x7fffu || !IsNanSubject(text)) return value;
|
||||
const std::uint64_t significand = 0xc000000000000000u;
|
||||
std::memcpy(&value, &significand, sizeof(significand));
|
||||
return value;
|
||||
std::uint64_t HexNanPayload(const char* open, const char*& end) {
|
||||
std::uint32_t words[2] = {};
|
||||
constexpr int high = 1;
|
||||
int top = high;
|
||||
int group = high;
|
||||
int digits = 0;
|
||||
int groupStart = 0;
|
||||
int inWord = 0;
|
||||
const auto* s = reinterpret_cast<const unsigned char*>(open);
|
||||
const auto skipBlanksAndPrefix = [&s](bool inside) {
|
||||
while (s[1] != 0 && s[1] <= ' ') ++s;
|
||||
if (inside && s[1] == 0) throw std::invalid_argument("strtod: blanks after a NAN payload digit reach the terminating NUL, where the console reads past it");
|
||||
if (s[1] == '0' && (s[2] == 'x' || s[2] == 'X') && s[3] > ' ') s += 2;
|
||||
};
|
||||
skipBlanksAndPrefix(false);
|
||||
for (unsigned char c; (c = *++s) != 0;) {
|
||||
const unsigned digit = HexNanDigit(c);
|
||||
if (digit == 0) {
|
||||
if (c <= ' ') {
|
||||
if (groupStart < digits) {
|
||||
if (top < group && inWord < 8) ShiftNanWords(words, top, group, inWord);
|
||||
if (top <= 0) {
|
||||
inWord = 8;
|
||||
continue;
|
||||
}
|
||||
groupStart = digits;
|
||||
words[--top] = 0;
|
||||
group = top;
|
||||
inWord = 0;
|
||||
}
|
||||
skipBlanksAndPrefix(true);
|
||||
continue;
|
||||
}
|
||||
if (c == ')' && digits != 0) {
|
||||
end = reinterpret_cast<const char*>(s + 1);
|
||||
break;
|
||||
}
|
||||
for (; c != 0; c = *++s) {
|
||||
if (c == ')') {
|
||||
end = reinterpret_cast<const char*>(s + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
++digits;
|
||||
if (++inWord > 8) {
|
||||
if (top <= 0) continue;
|
||||
inWord = 1;
|
||||
words[--top] = 0;
|
||||
}
|
||||
words[top] = (words[top] << 4) | (digit & 0xf);
|
||||
}
|
||||
if (digits == 0) return 0;
|
||||
if (top < group && inWord < 8) ShiftNanWords(words, top, group, inWord);
|
||||
if (top > 0) {
|
||||
int to = 0;
|
||||
int from = top;
|
||||
do words[to++] = words[from++];
|
||||
while (from <= high);
|
||||
do words[to++] = 0;
|
||||
while (to <= high);
|
||||
} else {
|
||||
words[high] &= 0xffffffffu >> 12;
|
||||
}
|
||||
if (words[0] == 0 && words[1] == 0) words[0] = 1;
|
||||
return (static_cast<std::uint64_t>(words[1]) << 32) | words[0];
|
||||
}
|
||||
|
||||
const char* NanSequenceEnd(const char* tail) {
|
||||
if (*tail != '(') return tail;
|
||||
const char* cursor = tail + 1;
|
||||
while ((*cursor >= '0' && *cursor <= '9') || ((*cursor | 0x20) >= 'a' && (*cursor | 0x20) <= 'z') || *cursor == '_') ++cursor;
|
||||
return *cursor == ')' ? cursor + 1 : tail;
|
||||
}
|
||||
|
||||
template<typename TFloat>
|
||||
TFloat NanValue(const NanSubject& subject, const char*& end) {
|
||||
if constexpr (std::is_same_v<TFloat, double>) {
|
||||
end = subject.tail;
|
||||
const std::uint64_t payload = *subject.tail == '(' ? HexNanPayload(subject.tail, end) : 0;
|
||||
return std::bit_cast<double>((subject.negative ? 0x8000000000000000ull : 0) | 0x7ff8000000000000ull | payload);
|
||||
} else if constexpr (std::is_same_v<TFloat, float>) {
|
||||
end = NanSequenceEnd(subject.tail);
|
||||
return std::bit_cast<float>(0x7fc00000u);
|
||||
} else {
|
||||
end = NanSequenceEnd(subject.tail);
|
||||
TFloat value{};
|
||||
const std::uint64_t significand = 0xc000000000000000u;
|
||||
const std::uint16_t signExponent = 0x7fff;
|
||||
std::memcpy(&value, &significand, sizeof(significand));
|
||||
std::memcpy(reinterpret_cast<unsigned char*>(&value) + sizeof(significand), &signExponent, sizeof(signExponent));
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TParse>
|
||||
@@ -75,10 +176,16 @@ auto ParseAsciiPrefix(const char16_t* text, char16_t** end, TParse parse) {
|
||||
|
||||
template<typename TFloat>
|
||||
TFloat ParseFloat(const char* text, char** end, TFloat (*parse)(const char*, char**)) {
|
||||
if (const NanSubject subject = FindNanSubject(text); subject.tail != nullptr) {
|
||||
const char* stop = subject.tail;
|
||||
const TFloat value = NanValue<TFloat>(subject, stop);
|
||||
if (end != nullptr) *end = const_cast<char*>(stop);
|
||||
return value;
|
||||
}
|
||||
char* parsedEnd = nullptr;
|
||||
const TFloat value = parse(text, &parsedEnd);
|
||||
if (end != nullptr) *end = parsedEnd;
|
||||
return parsedEnd == text ? TFloat{0} : QuietNan(value, text);
|
||||
return parsedEnd == text ? TFloat{0} : value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+113
-19
@@ -315,42 +315,136 @@ static bool ExtendedBits(long double value, std::uint64_t mantissa, std::uint16_
|
||||
}
|
||||
|
||||
static void CheckNanConversions() {
|
||||
const struct { const char* text; std::size_t consumed; bool negative; } cases[] = {
|
||||
{"nan(0x123)", 10, false}, {" -NaN(1)!", 8, true}, {"nan(0x800000)", 13, false},
|
||||
{"nan(0x10000000000000)", 21, false}, {"nan(0x8000000000000000)", 23, false},
|
||||
struct MeasuredNan {
|
||||
const char* text;
|
||||
std::uint64_t bits;
|
||||
std::size_t used;
|
||||
std::size_t sequenceUsed;
|
||||
bool extendedMeasured;
|
||||
bool wideDoubleMeasured;
|
||||
bool wideOtherMeasured;
|
||||
};
|
||||
static constexpr MeasuredNan cases[] = {
|
||||
{"nan", 0x7ff8000000000000ull, 3, 3, true, true, true},
|
||||
{"NAN", 0x7ff8000000000000ull, 3, 3, false, false, false},
|
||||
{"nan()", 0x7ff8000000000000ull, 5, 5, false, false, false},
|
||||
{"nan(1)", 0x7ff8000000000001ull, 6, 6, true, true, true},
|
||||
{"nan(0x10)", 0x7ff8000000000010ull, 9, 9, false, false, false},
|
||||
{"nan(abc)", 0x7ff8000000000abcull, 8, 8, false, false, false},
|
||||
{"nan(", 0x7ff8000000000000ull, 3, 3, true, true, true},
|
||||
{"-nan", 0xfff8000000000000ull, 4, 4, true, true, true},
|
||||
{"+nan(5)", 0x7ff8000000000005ull, 7, 7, false, false, false},
|
||||
{"nan(10)", 0x7ff8000000000010ull, 7, 7, false, false, false},
|
||||
{"nan(xyz)", 0x7ff8000000000000ull, 8, 8, false, false, false},
|
||||
{"nan(_)", 0x7ff8000000000000ull, 6, 6, false, false, false},
|
||||
{"nan(12", 0x7ff8000000000012ull, 3, 3, false, false, false},
|
||||
{"-nan(5)", 0xfff8000000000005ull, 7, 7, false, false, false},
|
||||
{"nan(0x8000000000000)", 0x7ff8000000000000ull, 20, 20, false, false, false},
|
||||
{"nan(0x10000000000000)", 0x7ff8000000000001ull, 21, 21, true, true, false},
|
||||
{"nan(0x400000)", 0x7ff8000000400000ull, 13, 13, false, false, false},
|
||||
{"NaN(0X1F)", 0x7ff800000000001full, 9, 9, false, false, false},
|
||||
{"nan(0x)", 0x7ff8000000000000ull, 7, 7, false, false, false},
|
||||
{"nan(123abc)", 0x7ff8000000123abcull, 11, 11, true, true, false},
|
||||
{"nan(1x2)", 0x7ff8000000000000ull, 8, 8, true, true, false},
|
||||
{"nan(0x1g)", 0x7ff8000000000000ull, 9, 9, true, true, false},
|
||||
{"nan(g1)", 0x7ff8000000000000ull, 7, 7, true, true, false},
|
||||
{"nan(0x0x1)", 0x7ff8000000000000ull, 10, 10, true, true, false},
|
||||
{"nan(fffffffffffff)", 0x7fffffffffffffffull, 18, 18, true, true, false},
|
||||
{"nan(1fffffffffffff)", 0x7fffffffffffffffull, 19, 19, true, true, false},
|
||||
{"nan(10000000000000)", 0x7ff8000000000001ull, 19, 19, true, true, false},
|
||||
{"nan(20000000000000)", 0x7ff8000000000001ull, 19, 19, true, true, false},
|
||||
{"nan(0x20000000000000)", 0x7ff8000000000001ull, 21, 21, true, true, false},
|
||||
{"nan(ffffffffffffffff)", 0x7fffffffffffffffull, 21, 21, true, true, false},
|
||||
{"nan(10000000000000000)", 0x7ff8000000000001ull, 22, 22, true, true, false},
|
||||
{"nan(0X)", 0x7ff8000000000000ull, 7, 7, true, true, false},
|
||||
{"nan(-1)", 0x7ff8000000000000ull, 7, 3, true, true, false},
|
||||
{"nan(+1)", 0x7ff8000000000000ull, 7, 3, true, true, false},
|
||||
{"nan( 1)", 0x7ff8000000000001ull, 7, 3, true, true, false},
|
||||
{"nan(1 )", 0x7ff8000100000000ull, 7, 3, true, true, false},
|
||||
{"nan(0)", 0x7ff8000000000001ull, 6, 6, true, true, false},
|
||||
{"nan(00001)", 0x7ff8000000000001ull, 10, 10, true, true, false},
|
||||
{"nan(0x0001)", 0x7ff8000000000001ull, 11, 11, true, true, false},
|
||||
{"nan(8000000000000)", 0x7ff8000000000000ull, 18, 18, true, true, false},
|
||||
{"nan(7ffffffffffff)", 0x7fffffffffffffffull, 18, 18, true, true, false},
|
||||
{"nan(123", 0x7ff8000000000123ull, 3, 3, true, true, false},
|
||||
{"nan(1)x", 0x7ff8000000000001ull, 6, 6, true, true, false},
|
||||
{"-nan(1)", 0xfff8000000000001ull, 7, 7, true, true, false},
|
||||
{"nan(abcdefABCDEF)", 0x7ff8abcdefabcdefull, 17, 17, true, true, false},
|
||||
{"nan(0xABCDEF)", 0x7ff8000000abcdefull, 13, 13, true, true, false},
|
||||
{"nan(9)", 0x7ff8000000000009ull, 6, 6, true, true, false},
|
||||
{"nan(1_2)", 0x7ff8000000000000ull, 8, 8, true, true, false},
|
||||
{"nan(_1)", 0x7ff8000000000000ull, 7, 7, true, true, false},
|
||||
{"nan(1)(2)", 0x7ff8000000000001ull, 6, 6, true, true, false},
|
||||
{"+nan", 0x7ff8000000000000ull, 4, 4, true, true, true},
|
||||
{"NaN", 0x7ff8000000000000ull, 3, 3, true, true, true},
|
||||
{"-NAN(5)", 0xfff8000000000005ull, 7, 7, true, true, true},
|
||||
{"nan(5)", 0x7ff8000000000005ull, 6, 6, true, true, true},
|
||||
{"-nan(abc)", 0xfff8000000000abcull, 9, 9, true, true, true},
|
||||
{" -nan", 0xfff8000000000000ull, 6, 6, true, true, true},
|
||||
{"nan(12 x", 0x7ff8000000000000ull, 3, 3, true, true, true},
|
||||
{"nan(12x", 0x7ff8000000000000ull, 3, 3, true, true, true},
|
||||
{"nan(1", 0x7ff8000000000001ull, 3, 3, true, true, true},
|
||||
{"nan(a b)", 0x7ff8000a0000000bull, 8, 3, true, true, true},
|
||||
{"nan(ab)", 0x7ff80000000000abull, 7, 7, true, true, true},
|
||||
{"nanx", 0x7ff8000000000000ull, 3, 3, true, true, true},
|
||||
{"-nan()", 0xfff8000000000000ull, 6, 6, true, true, true},
|
||||
{"nan(0x8000000000001)", 0x7ff8000000000001ull, 20, 20, true, true, true},
|
||||
};
|
||||
bool correct = true;
|
||||
for (const auto& test : cases) {
|
||||
const char* expectedEnd = test.text + test.consumed;
|
||||
const std::uint64_t expectedDouble = test.negative ? 0xfff8000000000000ull : 0x7ff8000000000000ull;
|
||||
char* end = nullptr;
|
||||
const auto value = std::bit_cast<std::uint64_t>(strtod_nid_postfix(test.text, &end));
|
||||
if (value != expectedDouble || end != expectedEnd) {
|
||||
if (value != test.bits || end != test.text + test.used) {
|
||||
std::fprintf(stderr, "strtod(\"%s\") = 0x%016llx\n", test.text, static_cast<unsigned long long>(value));
|
||||
correct = false;
|
||||
}
|
||||
const auto ascii = std::bit_cast<std::uint64_t>(atof_nid_postfix(test.text));
|
||||
if (ascii != expectedDouble) {
|
||||
std::fprintf(stderr, "atof(\"%s\") = 0x%016llx\n", test.text, static_cast<unsigned long long>(ascii));
|
||||
if (std::bit_cast<std::uint64_t>(atof_nid_postfix(test.text)) != test.bits) {
|
||||
std::fprintf(stderr, "atof(\"%s\")\n", test.text);
|
||||
correct = false;
|
||||
}
|
||||
const auto single = std::bit_cast<std::uint32_t>(strtof_nid_postfix(test.text, &end));
|
||||
if (single != (test.negative ? 0xffc00000u : 0x7fc00000u) || end != expectedEnd) {
|
||||
if (single != 0x7fc00000u || end != test.text + test.sequenceUsed) {
|
||||
std::fprintf(stderr, "strtof(\"%s\") = 0x%08x\n", test.text, static_cast<unsigned>(single));
|
||||
correct = false;
|
||||
}
|
||||
const long double extended = strtold_nid_postfix(test.text, &end);
|
||||
if (!ExtendedBits(extended, 0xc000000000000000ull, test.negative ? 0xffff : 0x7fff) || end != expectedEnd) {
|
||||
std::uint64_t significand = 0;
|
||||
std::uint16_t signExponent = 0;
|
||||
std::memcpy(&significand, &extended, sizeof(significand));
|
||||
std::memcpy(&signExponent, reinterpret_cast<const unsigned char*>(&extended) + sizeof(significand), sizeof(signExponent));
|
||||
std::fprintf(stderr, "strtold(\"%s\") = 0x%04x'%016llx\n", test.text, static_cast<unsigned>(signExponent),
|
||||
static_cast<unsigned long long>(significand));
|
||||
correct = false;
|
||||
if (test.extendedMeasured) {
|
||||
const long double extended = strtold_nid_postfix(test.text, &end);
|
||||
if (!ExtendedBits(extended, 0xc000000000000000ull, 0x7fff) || end != test.text + test.sequenceUsed) {
|
||||
std::fprintf(stderr, "strtold(\"%s\")\n", test.text);
|
||||
correct = false;
|
||||
}
|
||||
}
|
||||
char16_t wide[64] = {};
|
||||
for (std::size_t i = 0; test.text[i] != 0; ++i) wide[i] = static_cast<unsigned char>(test.text[i]);
|
||||
char16_t* wideEnd = nullptr;
|
||||
if (test.wideDoubleMeasured) {
|
||||
const auto wideValue = std::bit_cast<std::uint64_t>(wcstod_nid_postfix(wide, &wideEnd));
|
||||
if (wideValue != test.bits || wideEnd != wide + test.used) {
|
||||
std::fprintf(stderr, "wcstod(\"%s\") = 0x%016llx\n", test.text, static_cast<unsigned long long>(wideValue));
|
||||
correct = false;
|
||||
}
|
||||
}
|
||||
if (test.wideOtherMeasured) {
|
||||
const auto wideSingle = std::bit_cast<std::uint32_t>(wcstof_nid_postfix(wide, &wideEnd));
|
||||
const bool singleOk = wideSingle == 0x7fc00000u && wideEnd == wide + test.sequenceUsed;
|
||||
const long double wideExtended = wcstold_nid_postfix(wide, &wideEnd);
|
||||
if (!singleOk || !ExtendedBits(wideExtended, 0xc000000000000000ull, 0x7fff) || wideEnd != wide + test.sequenceUsed) {
|
||||
std::fprintf(stderr, "wcstof/wcstold(\"%s\")\n", test.text);
|
||||
correct = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Require(correct);
|
||||
for (const char* text : {"nan(1 ", "nan(12 \t"}) {
|
||||
bool threw = false;
|
||||
try { strtod_nid_postfix(text, nullptr); } catch (...) { threw = true; }
|
||||
Require(threw);
|
||||
}
|
||||
struct PlainNan { const char* text; std::uint64_t bits; std::size_t used; };
|
||||
for (const PlainNan& plain : {PlainNan{"nan(1 2)", 0x7ff8000100000002ull, 8}, PlainNan{"nan( \0)", 0x7ff8000000000000ull, 3}}) {
|
||||
char* end = nullptr;
|
||||
Require(std::bit_cast<std::uint64_t>(strtod_nid_postfix(plain.text, &end)) == plain.bits && end == plain.text + plain.used);
|
||||
}
|
||||
}
|
||||
|
||||
static void CheckHypotSpecialValue() {
|
||||
|
||||
@@ -322,12 +322,12 @@ int main() {
|
||||
const char16_t* half = u"-1.5é";
|
||||
require(wcstold_nid_postfix(half, &end) == -1.5L && end == half + 4);
|
||||
const char16_t* wideNan = u" -nan(0x123)é";
|
||||
require(std::bit_cast<std::uint64_t>(wcstod_nid_postfix(wideNan, &end)) == 0xfff8000000000000u && end == wideNan + 12);
|
||||
require(std::bit_cast<std::uint32_t>(wcstof_nid_postfix(wideNan, &end)) == 0xffc00000u && end == wideNan + 12);
|
||||
require(std::bit_cast<std::uint64_t>(wcstod_nid_postfix(wideNan, &end)) == 0xfff8000000000123u && end == wideNan + 12);
|
||||
require(std::bit_cast<std::uint32_t>(wcstof_nid_postfix(wideNan, &end)) == 0x7fc00000u && end == wideNan + 12);
|
||||
const long double wideExtended = wcstold_nid_postfix(wideNan, &end);
|
||||
std::uint64_t significand;
|
||||
std::memcpy(&significand, &wideExtended, sizeof(significand));
|
||||
require(std::isnan(wideExtended) && std::signbit(wideExtended) && significand == 0xc000000000000000u && end == wideNan + 12);
|
||||
require(std::isnan(wideExtended) && !std::signbit(wideExtended) && significand == 0xc000000000000000u && end == wideNan + 12);
|
||||
|
||||
require(wcscoll_nid_postfix(u"a", u"b") < 0);
|
||||
require(wcscoll_nid_postfix(u"", u"a") > 0);
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
- [fgetln](../../../core/libs/prx/libc/src/StandardStreams.cpp) (libSceLibcInternal, implemented in libc) throws: in the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x5FE80) the export is a `hlt` instruction, which faults in user mode and ends the process
|
||||
- [Wide-character conversions](../../../core/libs/prx/libc/src/Multibyte.cpp) (libc) - The wide `wcsto*` functions parse the ASCII prefix of the guest string, as the C locale accepts only ASCII white space and digits. [`wcsrtombs`](../../../core/libs/prx/libc/src/Multibyte.cpp) uses the guest's single-byte C locale through `wcrtomb`; other encodings and shift states are not supported
|
||||
- [realpath](../../../core/libs/prx/libc/src/RealPath.cpp) (libc) follows FreeBSD 11's [`realpath.c`](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/libc/stdlib/realpath.c) except for symlinks. The console's guest filesystem has none, so a host symlink under the guest root is followed for existence and type but not expanded, and `..` drops the previous component lexically, as `Resolve` does for every guest path. Through a host link `linkdir -> sub/deep`, `linkdir/../x` gives `x` beside the link, not beside its target.
|
||||
- [strtod, strtof, strtold](../../../core/libs/prx/libc/src/Strings.cpp) (libc) - `NAN(n-char-sequence)` converts to the quiet NaN that `nan` and `nanf` return, negated after a minus sign, and ignores the sequence as they ignore their tag. This is inferred from C17 7.12.11.2p2, which makes `nan("n")` equivalent to `strtod("NAN(n)", NULL)`, and not observed on the console. No console `nanl` was observed, so `strtold` returns the x87 default quiet NaN (exponent `0x7fff`, significand `0xc000000000000000`)
|
||||
- [strtod, strtof, strtold](../../../core/libs/prx/libc/src/Strings.cpp) (libc) - `strtod` and `wcstod` (and `atof`, which calls `strtod` here) read a `NAN(...)` sequence as FreeBSD 11's gdtoa [`hexnan`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/contrib/gdtoa/hexnan.c) does: hexadecimal digits with an optional `0x`, blanks starting a new 32-bit word, the first 16 digits kept and the payload cut to 52 bits, a zero payload made 1, any other character giving the default NaN but consuming up to the closing parenthesis, and an unterminated sequence keeping its payload while consuming only `nan`. The payload goes under the quiet bit with the sign, as in gdtoa's [`strtod`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/contrib/gdtoa/strtod.c#L316-L328). `strtof`, `wcstof`, `strtold` and `wcstold` return the positive default quiet NaN (`0x7fc00000`; exponent `0x7fff`, significand `0xc000000000000000`) whatever the sign and the sequence, and consume a sequence of letters, digits and underscores up to its closing parenthesis. The console's `libSceLibcInternal.sprx`, called from a payload process on a PS5 (firmware 12.70), gives these results for the 64 NaN inputs tried through `strtod` and `strtof`, 50 of them also through `strtold` and `wcstod`, and 18 through `wcstof` and `wcstold`, and, through `strtod`, for `nan(1 2)` and for `nan( ` followed by NUL and `)`; that a title's own `libc.prx` does the same is assumed. In `hexnan`, a blank after a payload digit, while a 32-bit word of the payload is still free, starts a blank skip that has no NUL check, so when only blanks follow up to the terminating NUL the console's `strtod` reads past it: `nan(1 ` followed by NUL and `)` consumes the `)` after the NUL, so the result depends on the bytes after the string, and here that case throws. A blank right after `nan(` is skipped with a NUL check, and the console stopped at the NUL there
|
||||
- [remquol](../../../core/libs/prx/libc/src/MathExact.cpp) (libc) leaves `*quo` unchanged when the result is NaN; the `libc.prx` bundled with PPSA08427 writes a value that depends on earlier x87 state (`remquol(1, 0)` stored 0 on a first call and 4 on later ones)
|
||||
- [Long double math in MathExact.cpp](../../../core/libs/prx/libc/src/MathExact.cpp) (libc) was compared with the `libc.prx` bundled with PPSA08427 only for canonical 80-bit encodings; unnormals, pseudo-denormals and pseudo-infinities passed to arithmetic functions (not the classifiers) follow the host x87 instead of the console
|
||||
- [nan and nanf](../../../core/libs/prx/libc/src/Math.cpp) (libc) return fixed quiet NaN values (`0x7fc00000` and `0x7ff8000000000000`), ignoring the tag argument as observed in the console's `libc.prx` (both exports load a global constant via `mov rax,[rip+..]; vmovsd xmm0,[rax]; ret`); VLC (PPSA85300) stops on nanf at start-up after strsep.
|
||||
|
||||
Reference in new issue
Block a user