fix(windows): va_list sysv abi parse for fomatting functions
This commit is contained in:
parent
e075f99757
commit
ea3f7b5ae0
|
|
@ -0,0 +1,209 @@
|
||||||
|
#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_WINDOWSFORMATTING_HPP
|
||||||
|
#define CORE_LIBS_PRX_LIBC_INCLUDE_WINDOWSFORMATTING_HPP
|
||||||
|
|
||||||
|
#include "SceTypes.hpp"
|
||||||
|
#include <cstdio>
|
||||||
|
#include <climits>
|
||||||
|
#include <cstring>
|
||||||
|
#include <limits>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace LibcDetail {
|
||||||
|
|
||||||
|
class FormatArguments {
|
||||||
|
VaList args;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FormatArguments(const void* source) {
|
||||||
|
std::memcpy(&args, source, sizeof(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T> T Next() {
|
||||||
|
const void* address;
|
||||||
|
if constexpr (std::is_same_v<T, double>) {
|
||||||
|
if (args.fp_offset < 176) {
|
||||||
|
address = static_cast<const char*>(args.reg_save_area) + args.fp_offset;
|
||||||
|
args.fp_offset += 16;
|
||||||
|
} else {
|
||||||
|
address = args.overflow_arg_area;
|
||||||
|
args.overflow_arg_area = static_cast<char*>(args.overflow_arg_area) + 8;
|
||||||
|
}
|
||||||
|
} else if constexpr (std::is_same_v<T, long double>) {
|
||||||
|
const auto aligned = (reinterpret_cast<std::uintptr_t>(args.overflow_arg_area) + 15) & ~std::uintptr_t(15);
|
||||||
|
address = reinterpret_cast<const void*>(aligned);
|
||||||
|
args.overflow_arg_area = reinterpret_cast<void*>(aligned + 16);
|
||||||
|
} else {
|
||||||
|
static_assert(sizeof(T) <= 8);
|
||||||
|
if (args.gp_offset < 48) {
|
||||||
|
address = static_cast<const char*>(args.reg_save_area) + args.gp_offset;
|
||||||
|
args.gp_offset += 8;
|
||||||
|
} else {
|
||||||
|
address = args.overflow_arg_area;
|
||||||
|
args.overflow_arg_area = static_cast<char*>(args.overflow_arg_area) + 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
T value;
|
||||||
|
std::memcpy(&value, address, sizeof(value));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class FormatOutput {
|
||||||
|
char* destination;
|
||||||
|
size_t capacity;
|
||||||
|
size_t count = 0;
|
||||||
|
std::string* complete;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FormatOutput(char* buffer, size_t size, std::string* text) : destination(buffer), capacity(size), complete(text) {
|
||||||
|
if (size && !buffer) throw std::invalid_argument("Null formatting buffer");
|
||||||
|
if (capacity) destination[0] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Append(const char* text, size_t size) {
|
||||||
|
if (size > static_cast<size_t>(INT_MAX) - count)
|
||||||
|
throw std::overflow_error("Formatted output exceeds INT_MAX");
|
||||||
|
if (capacity && count < capacity - 1) {
|
||||||
|
const size_t available = capacity - 1 - count;
|
||||||
|
const size_t copied = size < available ? size : available;
|
||||||
|
std::memcpy(destination + count, text, copied);
|
||||||
|
destination[count + copied] = 0;
|
||||||
|
}
|
||||||
|
if (complete) complete->append(text, size);
|
||||||
|
count += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T> void Value(const std::string& format, T value) {
|
||||||
|
const int size = std::snprintf(nullptr, 0, format.c_str(), value);
|
||||||
|
if (size < 0) throw std::runtime_error("Formatting conversion failed");
|
||||||
|
if (static_cast<size_t>(size) > static_cast<size_t>(INT_MAX) - count)
|
||||||
|
throw std::overflow_error("Formatted output exceeds INT_MAX");
|
||||||
|
if (complete) {
|
||||||
|
std::vector<char> text(static_cast<size_t>(size) + 1);
|
||||||
|
const int written = std::snprintf(text.data(), text.size(), format.c_str(), value);
|
||||||
|
if (written != size) throw std::runtime_error("Inconsistent formatting conversion");
|
||||||
|
complete->append(text.data(), static_cast<size_t>(size));
|
||||||
|
}
|
||||||
|
if (capacity && count < capacity - 1) {
|
||||||
|
const size_t remaining = capacity - count;
|
||||||
|
const size_t required = static_cast<size_t>(size) + 1;
|
||||||
|
const int written = std::snprintf(destination + count, remaining < required ? remaining : required, format.c_str(), value);
|
||||||
|
if (written != size) throw std::runtime_error("Inconsistent formatting conversion");
|
||||||
|
}
|
||||||
|
count += static_cast<size_t>(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Count() const { return static_cast<int>(count); }
|
||||||
|
};
|
||||||
|
|
||||||
|
inline int FormatWindows(char* buffer, size_t size, const char* format, const void* source, std::string* complete = nullptr) {
|
||||||
|
if (!format || !source) throw std::invalid_argument("Null formatting argument");
|
||||||
|
FormatArguments args(source);
|
||||||
|
FormatOutput output(buffer, size, complete);
|
||||||
|
while (*format) {
|
||||||
|
const char* literal = format;
|
||||||
|
while (*format && *format != '%') ++format;
|
||||||
|
output.Append(literal, static_cast<size_t>(format - literal));
|
||||||
|
if (!*format) break;
|
||||||
|
++format;
|
||||||
|
if (*format == '%') {
|
||||||
|
output.Append(format++, 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::string spec = "%";
|
||||||
|
while (*format && std::strchr("-+ #0", *format)) spec += *format++;
|
||||||
|
if (*format == '*') {
|
||||||
|
++format;
|
||||||
|
const int width = args.Next<int>();
|
||||||
|
if (width < 0) spec += '-';
|
||||||
|
spec += std::to_string(width < 0 ? -static_cast<long long>(width) : width);
|
||||||
|
} else {
|
||||||
|
while (*format >= '0' && *format <= '9') spec += *format++;
|
||||||
|
}
|
||||||
|
if (*format == '.') {
|
||||||
|
++format;
|
||||||
|
if (*format == '*') {
|
||||||
|
++format;
|
||||||
|
const int precision = args.Next<int>();
|
||||||
|
if (precision >= 0) spec += "." + std::to_string(precision);
|
||||||
|
} else {
|
||||||
|
spec += '.';
|
||||||
|
while (*format >= '0' && *format <= '9') spec += *format++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::string length;
|
||||||
|
if (*format && std::strchr("hljztL", *format)) {
|
||||||
|
length += *format++;
|
||||||
|
if ((length == "h" && *format == 'h') || (length == "l" && *format == 'l'))
|
||||||
|
length += *format++;
|
||||||
|
}
|
||||||
|
const char conversion = *format;
|
||||||
|
if (!conversion) throw std::invalid_argument("Incomplete format conversion");
|
||||||
|
++format;
|
||||||
|
const bool integerLength = length.empty() || length == "h" || length == "hh" ||
|
||||||
|
length == "l" || length == "ll" || length == "j" || length == "z" || length == "t";
|
||||||
|
if (conversion == 'd' || conversion == 'i') {
|
||||||
|
if (!integerLength) throw std::invalid_argument("Invalid integer length");
|
||||||
|
long long value;
|
||||||
|
if (length.empty() || length == "h" || length == "hh") {
|
||||||
|
value = args.Next<int>();
|
||||||
|
if (length == "h") value = static_cast<short>(value);
|
||||||
|
if (length == "hh") value = static_cast<signed char>(value);
|
||||||
|
} else value = args.Next<long long>();
|
||||||
|
output.Value(spec + "ll" + conversion, value);
|
||||||
|
} else if (std::strchr("ouxX", conversion)) {
|
||||||
|
if (!integerLength) throw std::invalid_argument("Invalid integer length");
|
||||||
|
unsigned long long value;
|
||||||
|
if (length.empty() || length == "h" || length == "hh") {
|
||||||
|
value = args.Next<unsigned int>();
|
||||||
|
if (length == "h") value = static_cast<unsigned short>(value);
|
||||||
|
if (length == "hh") value = static_cast<unsigned char>(value);
|
||||||
|
} else value = args.Next<unsigned long long>();
|
||||||
|
output.Value(spec + "ll" + conversion, value);
|
||||||
|
} else if (std::strchr("aAeEfFgG", conversion)) {
|
||||||
|
if (length == "L") {
|
||||||
|
static_assert(sizeof(long double) == 16);
|
||||||
|
static_assert(std::numeric_limits<long double>::digits == 64);
|
||||||
|
output.Value(spec + "L" + conversion, args.Next<long double>());
|
||||||
|
} else {
|
||||||
|
if (!length.empty() && length != "l") throw std::invalid_argument("Invalid floating length");
|
||||||
|
output.Value(spec + conversion, args.Next<double>());
|
||||||
|
}
|
||||||
|
} else if (conversion == 'c' && length.empty()) {
|
||||||
|
output.Value(spec + conversion, args.Next<int>());
|
||||||
|
} else if (conversion == 's' && length.empty()) {
|
||||||
|
const char* value = args.Next<const char*>();
|
||||||
|
if (!value) throw std::invalid_argument("Null formatted string");
|
||||||
|
output.Value(spec + conversion, value);
|
||||||
|
} else if (conversion == 'p' && length.empty()) {
|
||||||
|
output.Value(spec + conversion, args.Next<void*>());
|
||||||
|
} else if (conversion == 'n' && integerLength && spec == "%") {
|
||||||
|
void* pointer = args.Next<void*>();
|
||||||
|
if (!pointer) throw std::invalid_argument("Null format count pointer");
|
||||||
|
const int count = output.Count();
|
||||||
|
if (length == "hh") *static_cast<signed char*>(pointer) = static_cast<signed char>(count);
|
||||||
|
else if (length == "h") *static_cast<short*>(pointer) = static_cast<short>(count);
|
||||||
|
else if (length.empty()) *static_cast<int*>(pointer) = count;
|
||||||
|
else *static_cast<long long*>(pointer) = count;
|
||||||
|
} else {
|
||||||
|
throw std::invalid_argument("Unsupported format conversion");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output.Count();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int PrintWindows(const char* format, const void* args) {
|
||||||
|
std::string buffer;
|
||||||
|
const int size = FormatWindows(nullptr, 0, format, args, &buffer);
|
||||||
|
if (std::fwrite(buffer.data(), 1, static_cast<size_t>(size), stdout) != static_cast<size_t>(size))
|
||||||
|
throw std::runtime_error("Formatted output write failed");
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -5,8 +5,48 @@
|
||||||
#include "SceTypes.hpp"
|
#include "SceTypes.hpp"
|
||||||
#include "prx/libc/include/VarArgsAbi.hpp"
|
#include "prx/libc/include/VarArgsAbi.hpp"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include "prx/libc/include/WindowsFormatting.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
int APS5_VABI printf_nid_postfix(const char* format, ...) {
|
||||||
|
__builtin_sysv_va_list args;
|
||||||
|
__builtin_sysv_va_start(args, format);
|
||||||
|
const int result = LibcDetail::PrintWindows(format, args);
|
||||||
|
__builtin_sysv_va_end(args);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int APS5_VABI libc_printf_nid_postfix(const char* format, ...) {
|
||||||
|
__builtin_sysv_va_list args;
|
||||||
|
__builtin_sysv_va_start(args, format);
|
||||||
|
const int result = LibcDetail::PrintWindows(format, args);
|
||||||
|
__builtin_sysv_va_end(args);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int APS5_VABI snprintf_nid_postfix(char* buffer, size_t size, const char* format, ...) {
|
||||||
|
__builtin_sysv_va_list args;
|
||||||
|
__builtin_sysv_va_start(args, format);
|
||||||
|
const int result = LibcDetail::FormatWindows(buffer, size, format, args);
|
||||||
|
__builtin_sysv_va_end(args);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int APS5_VABI sprintf_nid_postfix(char* buffer, const char* format, ...) {
|
||||||
|
__builtin_sysv_va_list args;
|
||||||
|
__builtin_sysv_va_start(args, format);
|
||||||
|
const int result = LibcDetail::FormatWindows(buffer, SIZE_MAX, format, args);
|
||||||
|
__builtin_sysv_va_end(args);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
int APS5_VABI printf_nid_postfix(const char* format, ...) {
|
int APS5_VABI printf_nid_postfix(const char* format, ...) {
|
||||||
std::va_list args;
|
std::va_list args;
|
||||||
va_start(args, format);
|
va_start(args, format);
|
||||||
|
|
@ -55,6 +95,8 @@ int APS5_VABI sprintf_nid_postfix(VA_ARGS) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
int APS5_VABI sscanf_nid_postfix(VA_ARGS) {
|
int APS5_VABI sscanf_nid_postfix(VA_ARGS) {
|
||||||
LibcDetail::RegSaveArea regs;
|
LibcDetail::RegSaveArea regs;
|
||||||
LibcDetail::FillRegSaveArea(regs, rdx, rcx, r8, r9, 0, 0,
|
LibcDetail::FillRegSaveArea(regs, rdx, rcx, r8, r9, 0, 0,
|
||||||
|
|
@ -70,13 +112,21 @@ int APS5_VABI sscanf_nid_postfix(VA_ARGS) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int APS5_VABI vprintf_nid_postfix(const char* str, VaList* c) {
|
int APS5_VABI vprintf_nid_postfix(const char* str, VaList* c) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
return LibcDetail::PrintWindows(str, c);
|
||||||
|
#else
|
||||||
std::va_list* va = reinterpret_cast<std::va_list*>(c);
|
std::va_list* va = reinterpret_cast<std::va_list*>(c);
|
||||||
return std::vprintf(str, *va);
|
return std::vprintf(str, *va);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int APS5_VABI vsnprintf_nid_postfix(char* str, size_t size, const char* format, VaList* c) {
|
int APS5_VABI vsnprintf_nid_postfix(char* str, size_t size, const char* format, VaList* c) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
return LibcDetail::FormatWindows(str, size, format, c);
|
||||||
|
#else
|
||||||
std::va_list* va = reinterpret_cast<std::va_list*>(c);
|
std::va_list* va = reinterpret_cast<std::va_list*>(c);
|
||||||
return std::vsnprintf(str, size, format, *va);
|
return std::vsnprintf(str, size, format, *va);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int APS5_VABI puts_nid_postfix(const char* s) {
|
int APS5_VABI puts_nid_postfix(const char* s) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
#include "SceTypes.hpp"
|
||||||
|
#include <cstring>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
int APS5_VABI snprintf_nid_postfix(char*, size_t, const char*, ...);
|
||||||
|
int APS5_VABI sprintf_nid_postfix(char*, const char*, ...);
|
||||||
|
int APS5_VABI printf_nid_postfix(const char*, ...);
|
||||||
|
int APS5_VABI libc_printf_nid_postfix(const char*, ...);
|
||||||
|
int APS5_VABI vsnprintf_nid_postfix(char*, size_t, const char*, VaList*);
|
||||||
|
int APS5_VABI vprintf_nid_postfix(const char*, VaList*);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Require(bool condition) {
|
||||||
|
if (!condition) throw std::runtime_error("Formatting check failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int APS5_VABI FormatList(char* buffer, size_t size, const char* format, ...) {
|
||||||
|
__builtin_sysv_va_list args;
|
||||||
|
__builtin_sysv_va_start(args, format);
|
||||||
|
VaList list;
|
||||||
|
std::memcpy(&list, args, sizeof(list));
|
||||||
|
const VaList original = list;
|
||||||
|
const int result = vsnprintf_nid_postfix(buffer, size, format, &list);
|
||||||
|
Require(std::memcmp(&list, &original, sizeof(list)) == 0);
|
||||||
|
__builtin_sysv_va_end(args);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int APS5_VABI PrintList(const char* format, ...) {
|
||||||
|
__builtin_sysv_va_list args;
|
||||||
|
__builtin_sysv_va_start(args, format);
|
||||||
|
VaList list;
|
||||||
|
std::memcpy(&list, args, sizeof(list));
|
||||||
|
const int result = vprintf_nid_postfix(format, &list);
|
||||||
|
__builtin_sysv_va_end(args);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((noinline)) static void APS5_VABI RunChecks() {
|
||||||
|
char buffer[1024];
|
||||||
|
Require(FormatList(buffer, sizeof(buffer), "Mount requested: %d", 0) == 18);
|
||||||
|
Require(std::strcmp(buffer, "Mount requested: 0") == 0);
|
||||||
|
for (int index = 0; index < 10000; ++index) {
|
||||||
|
snprintf_nid_postfix(buffer, sizeof(buffer), "%d %d %d %d %d %d %d %d", 1, 2, 3, 4, 5, 6, 7, 8);
|
||||||
|
Require(std::strcmp(buffer, "1 2 3 4 5 6 7 8") == 0);
|
||||||
|
FormatList(buffer, sizeof(buffer), "%.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f %d", 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11);
|
||||||
|
Require(std::strcmp(buffer, "1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11") == 0);
|
||||||
|
snprintf_nid_postfix(buffer, sizeof(buffer), "%ld %lu %lld %zu %td %jd", -4294967297LL, 4294967297ULL, -5LL, 7ULL, -8LL, 9LL);
|
||||||
|
Require(std::strcmp(buffer, "-4294967297 4294967297 -5 7 -8 9") == 0);
|
||||||
|
snprintf_nid_postfix(buffer, sizeof(buffer), "%s:%*.*f:%d", "test", -8, 2, 1.25, 7);
|
||||||
|
Require(std::strcmp(buffer, "test:1.25 :7") == 0);
|
||||||
|
FormatList(buffer, sizeof(buffer), "%d %d %d %d %d %d %d %.3Lf %.1f", 1, 2, 3, 4, 5, 6, 7, 1.125L, 2.5);
|
||||||
|
Require(std::strcmp(buffer, "1 2 3 4 5 6 7 1.125 2.5") == 0);
|
||||||
|
}
|
||||||
|
snprintf_nid_postfix(buffer, sizeof(buffer), "%.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f", 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.);
|
||||||
|
Require(std::strcmp(buffer, "1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0") == 0);
|
||||||
|
snprintf_nid_postfix(buffer, sizeof(buffer), "%.3Lf %d %.1f", 1.125L, 7, 2.5);
|
||||||
|
Require(std::strcmp(buffer, "1.125 7 2.5") == 0);
|
||||||
|
char expected[1024];
|
||||||
|
std::snprintf(expected, sizeof(expected), "%#08x %.3e %a %g %p", 42u, 1.25, 1.25, 1.25, static_cast<void*>(buffer));
|
||||||
|
snprintf_nid_postfix(buffer, sizeof(buffer), "%#08x %.3e %a %g %p", 42u, 1.25, 1.25, 1.25, static_cast<void*>(buffer));
|
||||||
|
Require(std::strcmp(buffer, expected) == 0);
|
||||||
|
snprintf_nid_postfix(buffer, sizeof(buffer), "%.*s", -1, "unlimited");
|
||||||
|
Require(std::strcmp(buffer, "unlimited") == 0);
|
||||||
|
buffer[5] = '!';
|
||||||
|
Require(snprintf_nid_postfix(buffer, 5, "%s", "abcdef") == 6);
|
||||||
|
Require(std::strcmp(buffer, "abcd") == 0 && buffer[5] == '!');
|
||||||
|
Require(snprintf_nid_postfix(nullptr, 0, "%s:%d", "abcdef", 123) == 10);
|
||||||
|
buffer[0] = 'x';
|
||||||
|
Require(snprintf_nid_postfix(buffer, 1, "%d", 123) == 3 && buffer[0] == 0);
|
||||||
|
Require(sprintf_nid_postfix(buffer, "%hhd %hhu %hd %hu %%", 255, 257, 65535, 65537) == 11);
|
||||||
|
Require(std::strcmp(buffer, "-1 1 -1 1 %") == 0);
|
||||||
|
long long count = -1;
|
||||||
|
int smallCount = -1;
|
||||||
|
Require(snprintf_nid_postfix(buffer, 3, "abcd%lnEF%n", &count, &smallCount) == 6);
|
||||||
|
Require(count == 4 && smallCount == 6 && std::strcmp(buffer, "ab") == 0);
|
||||||
|
Require(snprintf_nid_postfix(buffer, sizeof(buffer), "a%cb", 0) == 3);
|
||||||
|
Require(buffer[0] == 'a' && buffer[1] == 0 && buffer[2] == 'b' && buffer[3] == 0);
|
||||||
|
Require(printf_nid_postfix("printf: %d %.1f\n", 7, 2.5) == 14);
|
||||||
|
Require(libc_printf_nid_postfix("libc_printf: %s\n", "OK") == 16);
|
||||||
|
Require(PrintList("vprintf: %d\n", 42) == 12);
|
||||||
|
std::puts("Formatting checks passed: 10000 iterations");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() { RunChecks(); }
|
||||||
Loading…
Reference in New Issue