Merge pull request #89 from jiangyy/nemu-new-api

Nemu new api
This commit is contained in:
Yanyan Jiang 2020-02-12 17:05:50 +08:00 committed by GitHub
commit 36679280c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 197 additions and 105 deletions

View File

@ -3,7 +3,7 @@
struct _Context {
union {
struct _AddressSpace *as;
void *as;
uint32_t gpr[32];
};
uint32_t lo, hi;

View File

@ -3,7 +3,7 @@
struct _Context {
union {
struct _AddressSpace *as;
void *pdir;
uint32_t gpr[32];
};
uint32_t cause;

View File

@ -2,11 +2,12 @@
#define __ARCH_H__
struct _Context {
struct _AddressSpace *as;
void *cr3;
uintptr_t edi, esi, ebp, esp;
uintptr_t ebx, edx, ecx, eax; // Register saved by pushal
int irq; // # of irq
uintptr_t err, eip, cs, eflags; // Execution state before trap
uintptr_t usp;
uintptr_t eip, cs, eflags; // Execution state before trap
};
#define GPR1 eax

View File

@ -11,7 +11,6 @@ void __am_tlb_refill(void);
_Context* __am_irq_handle(_Context *c) {
__am_get_cur_as(c);
_Context *next = c;
if (user_handler) {
_Event ev = {0};
uint32_t ex_code = (c->cause >> 2) & 0x1f;
@ -19,7 +18,7 @@ _Context* __am_irq_handle(_Context *c) {
switch (ex_code) {
case 0: ev.event = _EVENT_IRQ_TIMER; break;
case 2:
case 3: __am_tlb_refill(); return next;
case 3: __am_tlb_refill(); return c;
case 8:
syscall_instr = *(uint32_t *)(c->epc);
ev.event = ((syscall_instr >> 6) == 1) ? _EVENT_YIELD : _EVENT_SYSCALL;
@ -28,15 +27,13 @@ _Context* __am_irq_handle(_Context *c) {
default: ev.event = _EVENT_ERROR; break;
}
next = user_handler(ev, c);
if (next == NULL) {
next = c;
}
c = user_handler(ev, c);
assert(c != NULL);
}
__am_switch(next);
__am_switch(c);
return next;
return c;
}
extern void __am_asm_trap(void);
@ -52,16 +49,20 @@ int _cte_init(_Context*(*handler)(_Event, _Context*)) {
*(uint32_t *)0x80000000 = instr; // TLB refill exception
*(uint32_t *)(0x80000000 + 4) = 0; // delay slot
asm volatile("move $k0, $zero");
// register event handler
user_handler = handler;
return 0;
}
_Context *_kcontext(_Area stack, void (*entry)(void *), void *arg) {
_Context *c = (_Context*)stack.end - 1;
_Context *_kcontext(_Area kstack, void (*entry)(void *), void *arg) {
_Context *c = (_Context*)kstack.end - 1;
c->as = NULL;
c->epc = (uintptr_t)entry;
c->GPR2 = (uintptr_t)arg; // a0
c->gpr[29] = 0; // sp slot, used as usp
return c;
}

View File

@ -22,15 +22,22 @@ f(30) f(31)
#define CP0_CAUSE 13
#define CP0_EPC 14
#define SWAP(r1, r2, t) move $t, $r1; move $r1, $r2; move $r2, $t;
.set noat
.globl __am_asm_trap
__am_asm_trap:
move $k0, $sp
beqz $k0, in_kernel
SWAP(k0, sp, k1)
in_kernel:
addiu $sp, $sp, -CONTEXT_SIZE
MAP(REGS, PUSH)
sw $k0, OFFSET_SP($sp)
sw $k0, OFFSET_SP($sp) # k0 = (from user ? usp : 0)
move $k0, $zero
mflo $t0
mfhi $t1
mfc0 $t2, $CP0_CAUSE
@ -68,6 +75,12 @@ __am_asm_trap:
MAP(REGS, POP)
addiu $sp, $sp, CONTEXT_SIZE
lw $k0, OFFSET_SP($sp) # k0 = (from user ? usp : 0)
addiu $sp, $sp, CONTEXT_SIZE # ksp
beqz $k0, return
# return to user
SWAP(k0, sp, k1)
return:
eret

View File

@ -2,6 +2,7 @@
#include <klib.h>
#include <nemu.h>
#define USER_SPACE RANGE(0x40000000, 0x80000000)
#define PG_ALIGN __attribute((aligned(PGSIZE)))
static void* (*pgalloc_usr)(size_t) = NULL;
@ -16,35 +17,37 @@ int _vme_init(void* (*pgalloc_f)(size_t), void (*pgfree_f)(void*)) {
return 0;
}
int _protect(_AddressSpace *as) {
as->ptr = (PDE*)(pgalloc_usr(1));
return 0;
void _protect(_AddressSpace *as) {
as->ptr = (PDE*)(pgalloc_usr(PGSIZE));
as->pgsize = PGSIZE;
as->area = USER_SPACE;
}
void _unprotect(_AddressSpace *as) {
}
static _AddressSpace *cur_as = NULL;
static void *cur_as = NULL;
void __am_get_cur_as(_Context *c) {
c->as = cur_as;
}
void __am_tlb_clear();
void __am_switch(_Context *c) {
if (vme_enable) {
if (cur_as != NULL && cur_as->ptr != c->as->ptr) {
if (vme_enable && c->as != NULL) {
if (cur_as != c->as) {
__am_tlb_clear();
cur_as = c->as;
}
cur_as = c->as;
}
}
int _map(_AddressSpace *as, void *va, void *pa, int prot) {
void _map(_AddressSpace *as, void *va, void *pa, int prot) {
assert((uintptr_t)va % PGSIZE == 0);
assert((uintptr_t)pa % PGSIZE == 0);
PDE *pt = (PDE*)as->ptr;
PDE *pde = &pt[PDX(va)];
if (!(*pde & PTE_V)) {
*pde = PTE_V | (uint32_t)pgalloc_usr(1);
*pde = PTE_V | (uint32_t)pgalloc_usr(PGSIZE);
}
PTE *pte = &((PTE*)PTE_ADDR(*pde))[PTX(va)];
if (!(*pte & PTE_V)) {
@ -63,21 +66,15 @@ int _map(_AddressSpace *as, void *va, void *pa, int prot) {
asm volatile ("tlbwi");
}
}
return 0;
}
_Context *_ucontext(_AddressSpace *as, _Area kstack, void *entry) {
#warning "TODO";
return NULL;
/*
_Context *c = (_Context*)ustack.end - 1;
c->as = as;
_Context *c = (_Context*)kstack.end - 1;
c->as = as->ptr;
c->epc = (uintptr_t)entry;
c->status = 0x1;
c->gpr[29] = 1; // sp slot, used as usp, non-zero
return c;
*/
}
void __am_tlb_refill() {
@ -85,7 +82,8 @@ void __am_tlb_refill() {
asm volatile ("mfc0 %0, $10": "=r"(hi));
uint32_t va = hi & ~0x1fff;
PDE *pt = (PDE*)cur_as->ptr;
assert(cur_as != NULL);
PDE *pt = (PDE*)cur_as;
PDE *pde = &pt[PDX(va)];
// if (!(*pde & PTE_V)) {
// printf("hi = 0x%x, pt = 0x%x\n", hi, pt);

View File

@ -148,12 +148,12 @@ int _cte_init(_Context*(*handler)(_Event, _Context*)) {
return 0;
}
_Context* _kcontext(_Area stack, void (*entry)(void *), void *arg) {
_Context *c = (_Context*)stack.end - 1;
_Context* _kcontext(_Area kstack, void (*entry)(void *), void *arg) {
_Context *c = (_Context*)kstack.end - 1;
__am_get_example_uc(c);
c->uc.uc_mcontext.gregs[REG_RIP] = (uintptr_t)__am_kcontext_start;
c->uc.uc_mcontext.gregs[REG_RSP] = (uintptr_t)stack.end;
c->uc.uc_mcontext.gregs[REG_RSP] = (uintptr_t)kstack.end;
int ret = sigemptyset(&(c->uc.uc_sigmask)); // enable interrupt
assert(ret == 0);

View File

@ -7,6 +7,7 @@
#define MAX_CPU 16
#define TRAP_PAGE_START (void *)0x100000
#define PMEM_START (void *)0x3000000 // for nanos-lite with vme disabled
#define PMEM_SIZE (128 * 1024 * 1024) // 128MB
static int pmem_fd = 0;
static char pmem_shm_file[] = "/native-pmem-XXXXXX";
@ -61,7 +62,8 @@ static void init_platform() {
assert(pmem_fd != -1);
assert(0 == ftruncate(pmem_fd, PMEM_SIZE));
pmem = mmap(NULL, PMEM_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, pmem_fd, 0);
pmem = mmap(PMEM_START, PMEM_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_SHARED | MAP_FIXED, pmem_fd, 0);
assert(_heap.start != (void *)-1);
// allocate private per-cpu structure

View File

@ -1,6 +1,7 @@
#include <am.h>
#include <stdio.h>
#include <stdlib.h>
#include <klib-macros.h>
#define HEAP_SIZE (8 * 1024 * 1024)
static uint8_t heap[HEAP_SIZE] = {};

View File

@ -10,7 +10,6 @@ void __am_switch(_Context *c);
_Context* __am_irq_handle(_Context *c) {
__am_get_cur_as(c);
_Context *next = c;
if (user_handler) {
_Event ev = {0};
switch (c->cause) {
@ -22,15 +21,13 @@ _Context* __am_irq_handle(_Context *c) {
default: ev.event = _EVENT_ERROR; break;
}
next = user_handler(ev, c);
if (next == NULL) {
next = c;
}
c = user_handler(ev, c);
assert(c != NULL);
}
__am_switch(next);
__am_switch(c);
return next;
return c;
}
extern void __am_asm_trap(void);
@ -39,17 +36,22 @@ int _cte_init(_Context*(*handler)(_Event, _Context*)) {
// initialize exception entry
asm volatile("csrw stvec, %0" : : "r"(__am_asm_trap));
asm volatile("csrw sscratch, zero");
// register event handler
user_handler = handler;
return 0;
}
_Context *_kcontext(_Area stack, void (*entry)(void *), void *arg) {
_Context *c = (_Context*)stack.end - 1;
_Context *_kcontext(_Area kstack, void (*entry)(void *), void *arg) {
_Context *c = (_Context*)kstack.end - 1;
c->pdir = NULL;
c->epc = (uintptr_t)entry;
c->GPR2 = (uintptr_t)arg;
c->status = 0x000c0100;
c->gpr[2] = 0; // sp slot, used as usp
return c;
}

View File

@ -12,7 +12,7 @@ f(30) f(31)
#define PUSH(n) sw concat(x, n), (n * 4)(sp);
#define POP(n) lw concat(x, n), (n * 4)(sp);
#define CONTEXT_SIZE ((31 + 4) * 4)
#define CONTEXT_SIZE ((32 + 3) * 4)
#define OFFSET_SP ( 2 * 4)
#define OFFSET_CAUSE (32 * 4)
#define OFFSET_STATUS (33 * 4)
@ -20,12 +20,17 @@ f(30) f(31)
.globl __am_asm_trap
__am_asm_trap:
csrrw sp, sscratch, sp
bnez sp, save_context
# trap from kernel, restore the original sp
csrrw sp, sscratch, sp
save_context:
addi sp, sp, -CONTEXT_SIZE
MAP(REGS, PUSH)
mv t0, sp
addi t0, t0, CONTEXT_SIZE
csrrw t0, sscratch, x0 # t0 = (from user ? usp : 0)
sw t0, OFFSET_SP(sp)
csrr t0, scause
@ -50,4 +55,12 @@ __am_asm_trap:
addi sp, sp, CONTEXT_SIZE
csrw sscratch, sp # ksp
lw sp, (OFFSET_SP - CONTEXT_SIZE)(sp) # sp = (from user ? usp : 0)
bnez sp, return
# return to kernel
csrrw sp, sscratch, sp
return:
sret

View File

@ -15,10 +15,18 @@ static _Area segments[] = { // Kernel memory mappings
RANGE(MMIO_BASE, MMIO_BASE + MMIO_SIZE),
};
#define USER_SPACE RANGE(0x40000000, 0x80000000)
static inline void set_satp(void *pdir) {
asm volatile("csrw satp, %0" : : "r"(0x80000000 | ((uintptr_t)pdir >> 12)));
}
static inline uintptr_t get_satp() {
uintptr_t satp;
asm volatile("csrr %0, satp" : "=r"(satp));
return satp << 12;
}
int _vme_init(void* (*pgalloc_f)(size_t), void (*pgfree_f)(void*)) {
pgalloc_usr = pgalloc_f;
pgfree_usr = pgfree_f;
@ -53,51 +61,50 @@ int _vme_init(void* (*pgalloc_f)(size_t), void (*pgfree_f)(void*)) {
return 0;
}
int _protect(_AddressSpace *as) {
PDE *updir = (PDE*)(pgalloc_usr(1));
void _protect(_AddressSpace *as) {
PDE *updir = (PDE*)(pgalloc_usr(PGSIZE));
as->ptr = updir;
as->area = USER_SPACE;
as->pgsize = PGSIZE;
// map kernel space
for (int i = 0; i < NR_PDE; i ++) {
updir[i] = kpdirs[i];
}
return 0;
}
void _unprotect(_AddressSpace *as) {
}
static _AddressSpace *cur_as = NULL;
void __am_get_cur_as(_Context *c) {
c->as = cur_as;
c->pdir = (vme_enable ? (void *)get_satp() : NULL);
}
void __am_switch(_Context *c) {
if (vme_enable) {
set_satp(c->as->ptr);
cur_as = c->as;
if (vme_enable && c->pdir != NULL) {
set_satp(c->pdir);
}
}
int _map(_AddressSpace *as, void *va, void *pa, int prot) {
void _map(_AddressSpace *as, void *va, void *pa, int prot) {
assert((uintptr_t)va % PGSIZE == 0);
assert((uintptr_t)pa % PGSIZE == 0);
PDE *pt = (PDE*)as->ptr;
PDE *pde = &pt[PDX(va)];
if (!(*pde & PTE_V)) {
*pde = PTE_V | ((uint32_t)pgalloc_usr(1) >> PGSHFT << 10);
*pde = PTE_V | ((uint32_t)pgalloc_usr(PGSIZE) >> PGSHFT << 10);
}
PTE *pte = &((PTE*)PTE_ADDR(*pde))[PTX(va)];
if (!(*pte & PTE_V)) {
*pte = PTE_V | PTE_R | PTE_W | PTE_X | ((uint32_t)pa >> PGSHFT << 10);
}
return 0;
}
_Context *_ucontext(_AddressSpace *as, _Area ustack, _Area kstack, void *entry, void *args) {
_Context *c = (_Context*)ustack.end - 1;
_Context *_ucontext(_AddressSpace *as, _Area kstack, void *entry) {
_Context *c = (_Context*)kstack.end - 1;
c->as = as;
c->pdir = as->ptr;
c->epc = (uintptr_t)entry;
c->status = 0x000c0120;
c->gpr[2] = 1; // sp slot, used as usp, non-zero is ok
return c;
}

View File

@ -17,10 +17,18 @@ void __am_vecnull();
void __am_get_cur_as(_Context *c);
void __am_switch(_Context *c);
uintptr_t __am_ksp = 0;
_Context* __am_irq_handle(_Context *c) {
__am_get_cur_as(c);
_Context *next = c;
if (__am_ksp != 0) {
// trap from user
memcpy(&c->irq, (void *)__am_ksp, 5 * sizeof(uintptr_t));
c->usp = __am_ksp + 5 * sizeof(uintptr_t);
__am_ksp = 0;
}
if (user_handler) {
_Event ev = {0};
switch (c->irq) {
@ -30,15 +38,18 @@ _Context* __am_irq_handle(_Context *c) {
default: ev.event = _EVENT_ERROR; break;
}
next = user_handler(ev, c);
if (next == NULL) {
next = c;
}
c = user_handler(ev, c);
assert(c != NULL);
}
__am_switch(next);
__am_switch(c);
return next;
if (c->usp != 0) {
// return to user, set ksp for the next use
__am_ksp = (uintptr_t)(c + 1);
}
return c;
}
int _cte_init(_Context*(*handler)(_Event, _Context*)) {
@ -63,14 +74,18 @@ int _cte_init(_Context*(*handler)(_Event, _Context*)) {
return 0;
}
void _kcontext(_Context *c, _Area stack, void (*entry)(void *), void *arg) {
// stack.end -= 4 * sizeof(uintptr_t); // 4 = retaddr + argc + argv + envp
// uintptr_t *esp = stack.end;
// esp[1] = esp[2] = esp[3] = 0;
void __am_kcontext_start();
_Context* _kcontext(_Area kstack, void (*entry)(void *), void *arg) {
_Context *c = (_Context *)kstack.end - 1;
c->cr3 = NULL;
c->cs = 0x8;
c->eip = (uintptr_t)entry;
c->eip = (uintptr_t)__am_kcontext_start;
c->eflags = 0x2 | FL_IF;
c->usp = 0;
c->GPR1 = (uintptr_t)arg;
c->GPR2 = (uintptr_t)entry;
return c;
}
void _yield() {

View File

@ -1,10 +1,23 @@
#----|------------entry------------|-errorcode-|---irq id---|-----handler-----|
.globl __am_vecsys; __am_vecsys: pushl $0; pushl $0x80; jmp __am_asm_trap
.globl __am_vectrap; __am_vectrap: pushl $0; pushl $0x81; jmp __am_asm_trap
.globl __am_irq0; __am_irq0: pushl $0; pushl $32; jmp __am_asm_trap
.globl __am_vecnull; __am_vecnull: pushl $0; pushl $-1; jmp __am_asm_trap
#----|------------entry------------|---usp---|---irq id---|-----handler-----|
.globl __am_vecsys; __am_vecsys: pushl $0; pushl $0x80; jmp __am_asm_trap
.globl __am_vectrap; __am_vectrap: pushl $0; pushl $0x81; jmp __am_asm_trap
.globl __am_irq0; __am_irq0: pushl $0; pushl $32; jmp __am_asm_trap
.globl __am_vecnull; __am_vecnull: pushl $0; pushl $-1; jmp __am_asm_trap
.globl __am_kcontext_start
__am_kcontext_start:
pushl %eax
call *%ebx
__am_asm_trap:
cmpl $0, __am_ksp
je in_kernel
# switch to kernel stack
xchg %esp, __am_ksp
subl $20, %esp
in_kernel:
pushal
pushl $0
@ -16,6 +29,6 @@ __am_asm_trap:
addl $4, %esp
popal
addl $8, %esp
addl $4, %esp
iret
iret # customized with usp

View File

@ -1,5 +1,6 @@
#include <am.h>
#include <nemu.h>
#include <klib.h>
#include "x86-nemu.h"
typedef uint32_t PTE;
@ -29,6 +30,8 @@ static _Area segments[] = { // Kernel memory mappings
RANGE(MMIO_BASE, MMIO_BASE + MMIO_SIZE),
};
#define USER_SPACE RANGE(0x40000000, 0xc0000000)
int _vme_init(void* (*pgalloc_f)(size_t), void (*pgfree_f)(void*)) {
pgalloc_usr = pgalloc_f;
pgfree_usr = pgfree_f;
@ -66,8 +69,10 @@ int _vme_init(void* (*pgalloc_f)(size_t), void (*pgfree_f)(void*)) {
}
void _protect(_AddressSpace *as) {
PDE *updir = (PDE*)(pgalloc_usr(1));
PDE *updir = (PDE*)(pgalloc_usr(PGSIZE));
as->ptr = updir;
as->area = USER_SPACE;
as->pgsize = PGSIZE;
// map kernel space
for (int i = 0; i < NR_PDE; i ++) {
updir[i] = kpdirs[i];
@ -77,23 +82,21 @@ void _protect(_AddressSpace *as) {
void _unprotect(_AddressSpace *as) {
}
static _AddressSpace *cur_as = NULL;
void __am_get_cur_as(_Context *c) {
c->as = cur_as;
c->cr3 = (vme_enable ? (void *)get_cr3() : NULL);
}
void __am_switch(_Context *c) {
if (vme_enable) {
set_cr3(c->as->ptr);
cur_as = c->as;
}
if (vme_enable && c->cr3 != NULL) { set_cr3(c->cr3); }
}
void _map(_AddressSpace *as, void *va, void *pa, int prot) {
assert((uintptr_t)va % PGSIZE == 0);
assert((uintptr_t)pa % PGSIZE == 0);
PDE *pt = (PDE*)as->ptr;
PDE *pde = &pt[PDX(va)];
if (!(*pde & PTE_P)) {
*pde = PTE_P | PTE_W | PTE_U | (uint32_t)pgalloc_usr(1);
*pde = PTE_P | PTE_W | PTE_U | (uint32_t)pgalloc_usr(PGSIZE);
}
PTE *pte = &((PTE*)PTE_ADDR(*pde))[PTX(va)];
if (!(*pte & PTE_P)) {
@ -101,15 +104,12 @@ void _map(_AddressSpace *as, void *va, void *pa, int prot) {
}
}
void _ucontext(_Context *c, _AddressSpace *as, _Area kstack, void *entry) {
/*
ustack.end -= 4 * sizeof(uintptr_t); // 4 = retaddr + argc + argv + envp
uintptr_t *esp = ustack.end;
esp[1] = esp[2] = esp[3] = 0;
*/
c->as = as;
_Context* _ucontext(_AddressSpace *as, _Area kstack, void *entry) {
_Context *c = (_Context *)kstack.end - 1;
c->cr3 = as->ptr;
c->cs = 0x8;
c->eip = (uintptr_t)entry;
c->eflags = 0x2 | FL_IF;
c->usp = (uintptr_t)kstack.end; // non-zero but should be used to safely construct context
return c;
}

View File

@ -281,3 +281,19 @@ __umoddi3(du_int a, du_int b)
__udivmoddi4(a, b, &r);
return r;
}
#include <klib.h>
// for more details, refer to
// https://gcc.gnu.org/onlinedocs/gccint/Integer-library-routines.html
// only use for linking
int __clzsi2 (unsigned int a) {
assert(0);
}
int __ctzsi2 (unsigned int a) {
assert(0);
}

View File

@ -3,6 +3,10 @@
#include <klib.h>
#include <klib-macros.h>
#ifdef __LP64__
#define TEST_LONGLONG
#endif
#define PRINTABLE_CH " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
#define STRLEN(const_str) (sizeof(const_str) - 1) // sizeof counts the null byte
@ -13,7 +17,9 @@ ARRAY_DEF(char, char_, SCHAR_MAX, SCHAR_MIN, UCHAR_MAX);
ARRAY_DEF(short, short_, SHRT_MAX, SHRT_MIN, USHRT_MAX);
ARRAY_DEF(int, int_, INT_MAX, INT_MIN, UINT_MAX);
ARRAY_DEF(long, long_, LONG_MAX, LONG_MIN, ULONG_MAX); // wordsize-dependent
#ifdef TEST_LONGLONG
ARRAY_DEF(long long, longlong_, LLONG_MAX, LLONG_MIN, ULLONG_MAX);
#endif
ARRAY_DEF(uintptr_t, ptr, INTPTR_MAX, INTPTR_MIN, UINTPTR_MAX);// wordsize-dependent
static_assert(LENGTH(int_) == 8);
@ -99,8 +105,10 @@ INTEGER_TEST_DEF(test_sshort, "%hd", short_, "0 1927 32767 -32768 -32767 3855 -1
INTEGER_TEST_DEF(test_ushort, "%hu", short_, "0 1927 32767 32768 32769 3855 63609 65535")
INTEGER_TEST_DEF(test_slong, "%ld", long_, SLONG_REF)
INTEGER_TEST_DEF(test_ulong, "%lu", long_, ULONG_REF)
#ifdef TEST_LONGLONG
INTEGER_TEST_DEF(test_slonglong, "%lld", longlong_, SINT64_REF)
INTEGER_TEST_DEF(test_ulonglong, "%llu", longlong_, UINT64_REF)
#endif
static const char *full_format_ans[] = {
#include "full-format-glibc.h"
@ -153,8 +161,10 @@ void printf_test() {
test("length modifier %hu" , test_ushort);
test("length modifier %ld" , test_slong);
test("length modifier %lu" , test_ulong);
#ifdef TEST_LONGLONG
test("length modifier %lld" , test_slonglong);
test("length modifier %llu" , test_ulonglong);
#endif
test("full" , test_full_format);