[LTP] [PATCH v2 2/4] KVM: Implement strchr() and basic sprintf()

Martin Doucha
2024-05-14T12:09:05Z

Add basic implementation of sprintf() that supports string, pointer
and integer arguments but without advanced formatting options like
field alignment and padding.

Signed-off-by: Martin Doucha <mdoucha@suse.cz>
---

Changes since v1:
- #include <stdarg.h> instead of defining custom va_list macros

The C standard requires that <stdarg.h> must support freestanding environment
so it's safe to #include it in KVM guest code.

testcases/kernel/kvm/include/kvm_guest.h | 7 +
testcases/kernel/kvm/lib_guest.c | 312 +++++++++++++++++++++++
2 files changed, 319 insertions(+)

diff --git a/testcases/kernel/kvm/include/kvm_guest.h b/testcases/kernel/kvm/include/kvm_guest.h
index 96f246155..3cfafa313 100644
--- a/testcases/kernel/kvm/include/kvm_guest.h
+++ b/testcases/kernel/kvm/include/kvm_guest.h
@@ -8,6 +8,8 @@
#ifndef KVM_GUEST_H_
#define KVM_GUEST_H_

+#include <stdarg.h>
+
/* The main LTP include dir is intentionally excluded during payload build */
#include "../../../../include/tst_res_flags.h"
#undef TERRNO
@@ -49,6 +51,11 @@ void *memcpy(void *dest, const void *src, size_t size);
char *strcpy(char *dest, const char *src);
char *strcat(char *dest, const char *src);
size_t strlen(const char *str);
+char *strchr(const char *s, int c);
+char *strrchr(const char *s, int c);
+
+int vsprintf(char *dest, const char *fmt, va_list ap);
+int sprintf(char *dest, const char *fmt, ...);

/* Exit the VM by looping on a HLT instruction forever */
void kvm_exit(void) __attribute__((noreturn));
diff --git a/testcases/kernel/kvm/lib_guest.c b/testcases/kernel/kvm/lib_guest.c
index f3e21d3d6..73a76ccb1 100644
--- a/testcases/kernel/kvm/lib_guest.c
+++ b/testcases/kernel/kvm/lib_guest.c
@@ -76,6 +76,74 @@ size_t strlen(const char *str)
return ret;
}

+char *strchr(const char *s, int c)
+{
+ for (; *s; s++) {
+ if (*s == c)
+ return (char *)s;
+ }
+
+ return NULL;
+}
+
+char *strrchr(const char *s, int c)
+{
+ const char *ret = NULL;
+
+ for (; *s; s++) {
+ if (*s == c)
+ ret = s;
+ }
+
+ return (char *)ret;
+}
+
+#if defined(__x86_64__) && !defined(__ILP32__)
+uint64_t u64divu16(uint64_t a, uint16_t b)
+{
+ return a / b;
+}
+
+unsigned int u64modu16(uint64_t a, uint16_t b)
+{
+ return a % b;
+}
+
+#else /* defined(__x86_64__) && !defined(__ILP32__) */
+
+/* u64 short division helpers to avoid need to link libgcc on 32bit archs */
+uint64_t u64divu16(uint64_t a, uint16_t b)
+{
+ uint64_t ret = 0;
+ uint32_t tmp = a >> 32;
+
+ ret = tmp / b;
+ ret <<= 32;
+ tmp %= b;
+ tmp <<= 16;
+ tmp |= (a >> 16) & 0xffff;
+ ret |= (tmp / b) << 16;
+ tmp %= b;
+ tmp <<= 16;
+ tmp |= a & 0xffff;
+ ret |= tmp / b;
+ return ret;
+}
+
+unsigned int u64modu16(uint64_t a, uint16_t b)
+{
+ uint32_t tmp = a >> 32;
+
+ tmp %= b;
+ tmp <<= 16;
+ tmp |= (a >> 16) & 0xffff;
+ tmp %= b;
+ tmp <<= 16;
+ tmp |= a & 0xffff;
+ return tmp % b;
+}
+#endif /* defined(__x86_64__) && !defined(__ILP32__) */
+
char *ptr2hex(char *dest, uintptr_t val)
{
unsigned int i;
@@ -95,6 +163,250 @@ char *ptr2hex(char *dest, uintptr_t val)
return ret;
}

+char *u64tostr(char *dest, uint64_t val, uint16_t base, int caps)
+{
+ unsigned int i;
+ uintptr_t tmp = u64divu16(val, base);
+ char hex = caps ? 'A' : 'a';
+ char *ret = dest;
+
+ for (i = 1; tmp; i++, tmp = u64divu16(tmp, base))
+ ;
+
+ dest[i] = '\0';
+
+ do {
+ tmp = u64modu16(val, base);
+ dest[--i] = tmp + (tmp >= 10 ? hex - 10 : '0');
+ val = u64divu16(val, base);
+ } while (i);
+
+ return ret;
+}
+
+char *i64tostr(char *dest, int64_t val)
+{
+ if (val < 0) {
+ dest[0] = '-';
+ u64tostr(dest + 1, -val, 10, 0);
+ return dest;
+ }
+
+ return u64tostr(dest, val, 10, 0);
+}
+
+int vsprintf(char *dest, const char *fmt, va_list ap)
+{
+ va_list args;
+ int ret = 0;
+ char conv;
+ uint64_t u64val = 0;
+ int64_t i64val = 0;
+ const char * const uint_conv = "ouxX";
+
+ va_copy(args, ap);
+
+ for (; *fmt; fmt++) {
+ if (*fmt != '%') {
+ dest[ret++] = *fmt;
+ continue;
+ }
+
+ conv = 0;
+ fmt++;
+
+ switch (*fmt) {
+ case '%':
+ dest[ret++] = *fmt;
+ break;
+
+ case 'c':
+ dest[ret++] = va_arg(args, int);
+ break;
+
+ case 's':
+ strcpy(dest + ret, va_arg(args, const char *));
+ ret += strlen(dest + ret);
+ break;
+
+ case 'p':
+ strcpy(dest + ret, "0x");
+ ptr2hex(dest + ret + 2,
+ (uintptr_t)va_arg(args, void *));
+ ret += strlen(dest + ret);
+ break;
+
+ case 'l':
+ fmt++;
+
+ switch (*fmt) {
+ case 'l':
+ fmt++;
+
+ if (*fmt == 'd' || *fmt == 'i') {
+ i64val = va_arg(args, long long);
+ conv = *fmt;
+ break;
+ }
+
+ if (strchr(uint_conv, *fmt)) {
+ u64val = va_arg(args,
+ unsigned long long);
+ conv = *fmt;
+ break;
+ }
+
+ va_end(args);
+ return -1;
+
+ case 'd':
+ case 'i':
+ i64val = va_arg(args, long);
+ conv = *fmt;
+ break;
+
+ default:
+ if (strchr(uint_conv, *fmt)) {
+ u64val = va_arg(args,
+ unsigned long);
+ conv = *fmt;
+ break;
+ }
+
+ va_end(args);
+ return -1;
+ }
+ break;
+
+ case 'h':
+ fmt++;
+
+ switch (*fmt) {
+ case 'h':
+ fmt++;
+
+ if (*fmt == 'd' || *fmt == 'i') {
+ i64val = (signed char)va_arg(args, int);
+ conv = *fmt;
+ break;
+ }
+
+ if (strchr(uint_conv, *fmt)) {
+ u64val = (unsigned char)va_arg(args,
+ unsigned int);
+ conv = *fmt;
+ break;
+ }
+
+ va_end(args);
+ return -1;
+
+ case 'd':
+ case 'i':
+ i64val = (short int)va_arg(args, int);
+ conv = *fmt;
+ break;
+
+ default:
+ if (strchr(uint_conv, *fmt)) {
+ u64val = (unsigned short int)va_arg(
+ args, unsigned int);
+ conv = *fmt;
+ break;
+ }
+
+ va_end(args);
+ return -1;
+ }
+ break;
+
+ case 'z':
+ fmt++;
+
+ if (*fmt == 'd' || *fmt == 'i') {
+ i64val = va_arg(args, ssize_t);
+ conv = *fmt;
+ break;
+ }
+
+ if (strchr(uint_conv, *fmt)) {
+ u64val = va_arg(args, size_t);
+ conv = *fmt;
+ break;
+ }
+
+ va_end(args);
+ return -1;
+
+ case 'd':
+ case 'i':
+ i64val = va_arg(args, int);
+ conv = *fmt;
+ break;
+
+ default:
+ if (strchr(uint_conv, *fmt)) {
+ u64val = va_arg(args, unsigned int);
+ conv = *fmt;
+ break;
+ }
+
+ va_end(args);
+ return -1;
+ }
+
+ switch (conv) {
+ case 0:
+ continue;
+
+ case 'd':
+ case 'i':
+ i64tostr(dest + ret, i64val);
+ ret += strlen(dest + ret);
+ break;
+
+ case 'o':
+ u64tostr(dest + ret, u64val, 8, 0);
+ ret += strlen(dest + ret);
+ break;
+
+ case 'u':
+ u64tostr(dest + ret, u64val, 10, 0);
+ ret += strlen(dest + ret);
+ break;
+
+ case 'x':
+ u64tostr(dest + ret, u64val, 16, 0);
+ ret += strlen(dest + ret);
+ break;
+
+ case 'X':
+ u64tostr(dest + ret, u64val, 16, 1);
+ ret += strlen(dest + ret);
+ break;
+
+ default:
+ va_end(args);
+ return -1;
+ }
+ }
+
+ va_end(args);
+ dest[ret++] = '\0';
+ return ret;
+}
+
+int sprintf(char *dest, const char *fmt, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start(args, fmt);
+ ret = vsprintf(dest, fmt, args);
+ va_end(args);
+ return ret;
+}
+
void *tst_heap_alloc_aligned(size_t size, size_t align)
{
uintptr_t addr = (uintptr_t)heap_end;