Files
xen/tools/xentrace/xenctx.c
T
awilliam@xenbuild.aw c59ad25b3b [IA64] xenctx shows more registers for ia64
This patch adds more user registers to show them to xenctx for ia64.
Tested domU/domVTi on ia64.

Sample is the below.
# ./xenctx 1 0
 iip:               e000000000000810  
 ipsr:              00001012087a6010   b0:                a000000100068a70
 b6:                a00000010014ff60   b7:                e000000000000800
 cr_ifs:            800000000000050a   ar_unat:           0000000000000000
 ar_pfs:            8000000000000209   ar_rsc:            0000000000000008
 ar_rnat:           0000000000000000   ar_bspstore:       a000000100c19030
 ar_fpsr:           0009804c8a70433f   event_callback_ip: a000000100067a20
 pr:                000000000005aa85   loadrs:            0000000000780000
 iva:               a000000100008000   dcr:               0000000000007e04

 r1:  a0000001010369a0
 r2:  0000000000001000   r3:  8000000000000209
 r4:  0000000000000000   r5:  0000000000000000
 r6:  0000000000000000   r7:  0000000000000000
 r8:  a000000100068a70   r9:  0000000000000100
 r10: 0000000000000000   r11: 0000000000050ac5
 sp:  a000000100c1fd80   tp:  a000000100c18000
 r14: 0000000000000001   r15: 0000000000000000
 r16: fffffffffff04c18   r17: a000000100c1fdb0
 r18: a000000100c1fdb1   r19: a000000100c1fe90
 r20: a000000100c1fe10   r21: 0000000000000000
 r22: 0000000000000001   r23: 0000000000000000
 r24: a000000100e5a448   r25: a000000100c18f10
 r26: ffffffffffff0030   r27: 0000000000000000
 r28: 000000000000001d   r29: 0000000000000000
 r30: 0000000000000000   r31: 0000000000000000

 itr: P rid    va               pa            ps      ed pl ar a d ma    key
 [0]  1 000005 a000000100000000 0000004000000 1a  64M 1  2  3  1 1 0 WB  000000
 [1]  1 000007 e000000000000000 0000000000000 18  16M 1  2  3  1 1 0 WB  000000
 [2]  0 000000 0000000000000000 0000000000000 00      0  0  0  0 0 0 WB  000000
 [3]  0 000000 0000000000000000 0000000000000 00      0  0  0  0 0 0 WB  000000
 [4]  0 000000 0000000000000000 0000000000000 00      0  0  0  0 0 0 WB  000000
 [5]  0 000000 0000000000000000 0000000000000 00      0  0  0  0 0 0 WB  000000
 [6]  0 000000 0000000000000000 0000000000000 00      0  0  0  0 0 0 WB  000000
 [7]  0 000000 0000000000000000 0000000000000 00      0  0  0  0 0 0 WB  000000

 dtr: P rid    va               pa            ps      ed pl ar a d ma    key
 [0]  1 000005 a000000100000000 0000004000000 1a  64M 1  2  3  1 1 0 WB  000000
 [1]  1 000007 ffffffffffff0000 0000000010000 10  64K 1  2  3  1 1 0 WB  000000
 [2]  1 000007 e000000000000000 0000000000000 18  16M 1  2  3  1 1 0 WB  000000
 [3]  0 000000 0000000000000000 0000000000000 00      0  0  0  0 0 0 WB  000000
 [4]  0 000000 0000000000000000 0000000000000 00      0  0  0  0 0 0 WB  000000
 [5]  0 000000 0000000000000000 0000000000000 00      0  0  0  0 0 0 WB  000000
 [6]  0 000000 0000000000000000 0000000000000 00      0  0  0  0 0 0 WB  000000
 [7]  0 000000 0000000000000000 0000000000000 00      0  0  0  0 0 0 WB  000000

Signed-off-by: Akio Takebe <takebe_akio@jp.fujitsu.com>
Signed-off-by: Masaki Kanno <kanno.masaki@jp.fujitsu.com>
2006-10-24 10:08:30 -06:00

653 lines
19 KiB
C

/******************************************************************************
* tools/xentrace/xenctx.c
*
* Tool for dumping the cpu context
*
* Copyright (C) 2005 by Intel Corp
*
* Author: Arun Sharma <arun.sharma@intel.com>
* Date: February 2005
*/
#include <time.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <argp.h>
#include <signal.h>
#include <string.h>
#include <getopt.h>
#include "xenctrl.h"
int xc_handle = 0;
int domid = 0;
int frame_ptrs = 0;
int stack_trace = 0;
#if defined (__i386__)
#define FMT_SIZE_T "%08x"
#define STACK_POINTER(regs) (regs->esp)
#define FRAME_POINTER(regs) (regs->ebp)
#define INSTR_POINTER(regs) (regs->eip)
#define STACK_ROWS 4
#define STACK_COLS 8
#elif defined (__x86_64__)
#define FMT_SIZE_T "%016lx"
#define STACK_POINTER(regs) (regs->rsp)
#define FRAME_POINTER(regs) (regs->rbp)
#define INSTR_POINTER(regs) (regs->rip)
#define STACK_ROWS 4
#define STACK_COLS 4
#elif defined (__ia64__)
#define FMT_SIZE_T "%016lx"
#define STACK_POINTER(regs) (regs->r12)
#define FRAME_POINTER(regs) 0
#define INSTR_POINTER(regs) (regs->cr_iip)
#define STACK_ROWS 4
#define STACK_COLS 4
/* On ia64, we can't translate virtual address to physical address. */
#define NO_TRANSLATION
#endif
struct symbol {
size_t address;
char type;
char *name;
struct symbol *next;
} *symbol_table = NULL;
size_t kernel_stext, kernel_etext, kernel_sinittext, kernel_einittext, kernel_hypercallpage;
int is_kernel_text(size_t addr)
{
#if defined (__i386__)
if (symbol_table == NULL)
return (addr > 0xc000000);
#elif defined (__x86_64__)
if (symbol_table == NULL)
return (addr > 0xffffffff80000000UL);
#elif defined (__ia64__)
if (symbol_table == NULL)
return (addr > 0xa000000000000000UL);
#endif
if (addr >= kernel_stext &&
addr <= kernel_etext)
return 1;
if (addr >= kernel_hypercallpage &&
addr <= kernel_hypercallpage + 4096)
return 1;
if (addr >= kernel_sinittext &&
addr <= kernel_einittext)
return 1;
return 0;
}
void free_symbol(struct symbol *symbol)
{
if (symbol == NULL)
return;
if (symbol->name)
free(symbol->name);
free(symbol);
}
void insert_symbol(struct symbol *symbol)
{
static struct symbol *prev = NULL;
struct symbol *s = symbol_table;
if (s == NULL) {
symbol_table = symbol;
symbol->next = NULL;
return;
}
/* The System.map is usually already sorted... */
if (prev
&& prev->address < symbol->address
&& (!prev->next || prev->next->address > symbol->address)) {
s = prev;
} else {
/* ... otherwise do crappy/slow search for the correct place */
while(s && s->next && s->next->address < symbol->address)
s = s->next;
}
symbol->next = s->next;
s->next = symbol;
prev = symbol;
}
struct symbol *lookup_symbol(size_t address)
{
struct symbol *s = symbol_table;
while(s && s->next && s->next->address < address)
s = s->next;
if (s && s->address < address)
return s;
return NULL;
}
void print_symbol(size_t addr)
{
struct symbol *s;
if (!is_kernel_text(addr))
return;
s = lookup_symbol(addr);
if (s==NULL)
return;
if (addr==s->address)
printf("%s", s->name);
else
printf("%s+%#x", s->name, (unsigned int)(addr - s->address));
}
void read_symbol_table(const char *symtab)
{
char line[256];
char *p;
struct symbol *symbol;
FILE *f;
f = fopen(symtab, "r");
if(f == NULL) {
fprintf(stderr, "failed to open symbol table %s\n", symtab);
exit(-1);
}
while(!feof(f)) {
if(fgets(line,256,f)==NULL)
break;
symbol = malloc(sizeof(*symbol));
/* need more checks for syntax here... */
symbol->address = strtoull(line, &p, 16);
p++;
symbol->type = *p++;
p++;
/* in the future we should handle the module name
* being appended here, this would allow us to use
* /proc/kallsyms as our symbol table
*/
if (p[strlen(p)-1] == '\n')
p[strlen(p)-1] = '\0';
symbol->name = strdup(p);
insert_symbol(symbol);
if (strcmp(symbol->name, "_stext") == 0)
kernel_stext = symbol->address;
else if (strcmp(symbol->name, "_etext") == 0)
kernel_etext = symbol->address;
else if (strcmp(symbol->name, "_sinittext") == 0)
kernel_sinittext = symbol->address;
else if (strcmp(symbol->name, "_einittext") == 0)
kernel_einittext = symbol->address;
else if (strcmp(symbol->name, "hypercall_page") == 0)
kernel_hypercallpage = symbol->address;
}
fclose(f);
}
#ifdef __i386__
void print_ctx(vcpu_guest_context_t *ctx1)
{
struct cpu_user_regs *regs = &ctx1->user_regs;
printf("eip: %08x ", regs->eip);
print_symbol(regs->eip);
printf("\n");
printf("esp: %08x\n", regs->esp);
printf("eax: %08x\t", regs->eax);
printf("ebx: %08x\t", regs->ebx);
printf("ecx: %08x\t", regs->ecx);
printf("edx: %08x\n", regs->edx);
printf("esi: %08x\t", regs->esi);
printf("edi: %08x\t", regs->edi);
printf("ebp: %08x\n", regs->ebp);
printf(" cs: %08x\t", regs->cs);
printf(" ds: %08x\t", regs->ds);
printf(" fs: %08x\t", regs->fs);
printf(" gs: %08x\n", regs->gs);
}
#elif defined(__x86_64__)
void print_ctx(vcpu_guest_context_t *ctx1)
{
struct cpu_user_regs *regs = &ctx1->user_regs;
printf("rip: %08lx ", regs->rip);
print_symbol(regs->rip);
printf("\n");
printf("rsp: %08lx\n", regs->rsp);
printf("rax: %08lx\t", regs->rax);
printf("rbx: %08lx\t", regs->rbx);
printf("rcx: %08lx\t", regs->rcx);
printf("rdx: %08lx\n", regs->rdx);
printf("rsi: %08lx\t", regs->rsi);
printf("rdi: %08lx\t", regs->rdi);
printf("rbp: %08lx\n", regs->rbp);
printf(" r8: %08lx\t", regs->r8);
printf(" r9: %08lx\t", regs->r9);
printf("r10: %08lx\t", regs->r10);
printf("r11: %08lx\n", regs->r11);
printf("r12: %08lx\t", regs->r12);
printf("r13: %08lx\t", regs->r13);
printf("r14: %08lx\t", regs->r14);
printf("r15: %08lx\n", regs->r15);
printf(" cs: %08x\t", regs->cs);
printf(" ds: %08x\t", regs->ds);
printf(" fs: %08x\t", regs->fs);
printf(" gs: %08x\n", regs->gs);
}
#elif defined(__ia64__)
#define PTE_ED_SHIFT 52
#define PTE_ED_MASK 1
#define PTE_PPN_SHIFT 12
#define PTE_PPN_MASK 0x3fffffffff
#define PTE_AR_SHIFT 9
#define PTE_AR_MASK 7
#define PTE_PL_SHIFT 7
#define PTE_PL_MASK 3
#define PTE_D_SHIFT 6
#define PTE_D_MASK 1
#define PTE_A_SHIFT 5
#define PTE_A_MASK 1
#define PTE_MA_SHIFT 2
#define PTE_MA_MASK 7
#define PTE_P_SHIFT 0
#define PTE_P_MASK 1
#define ITIR_KEY_SHIFT 8
#define ITIR_KEY_MASK 0xffffff
#define ITIR_PS_SHIFT 2
#define ITIR_PS_MASK 0x3f
#define ITIR_PS_MIN 12
#define ITIR_PS_MAX 28
#define RR_RID_SHIFT 8
#define RR_RID_MASK 0xffffff
void print_ctx(vcpu_guest_context_t *ctx1)
{
struct cpu_user_regs *regs = &ctx1->user_regs;
struct vcpu_extra_regs *er = &ctx1->extra_regs;
int i, ps_val, ma_val;
unsigned long pa;
const char ps[][5] = {" 4K", " 8K", " 16K", " ",
" 64K", " ", "256K", " ",
" 1M", " ", " 4M", " ",
" 16M", " ", " 64M", " ",
"256M"};
const char ma[][4] = {"WB ", " ", " ", " ",
"UC ", "UCE", "WC ", "Nat"};
printf(" iip: %016lx ", regs->cr_iip);
print_symbol(regs->cr_iip);
printf("\n");
printf(" ipsr: %016lx ", regs->cr_ipsr);
printf(" b0: %016lx\n", regs->b0);
printf(" b6: %016lx ", regs->b6);
printf(" b7: %016lx\n", regs->b7);
printf(" cr_ifs: %016lx ", regs->cr_ifs);
printf(" ar_unat: %016lx\n", regs->ar_unat);
printf(" ar_pfs: %016lx ", regs->ar_pfs);
printf(" ar_rsc: %016lx\n", regs->ar_rsc);
printf(" ar_rnat: %016lx ", regs->ar_rnat);
printf(" ar_bspstore: %016lx\n", regs->ar_bspstore);
printf(" ar_fpsr: %016lx ", regs->ar_fpsr);
printf(" event_callback_ip: %016lx\n", er->event_callback_ip);
printf(" pr: %016lx ", regs->pr);
printf(" loadrs: %016lx\n", regs->loadrs);
printf(" iva: %016lx ", er->iva);
printf(" dcr: %016lx\n", er->dcr);
printf("\n");
printf(" r1: %016lx\n", regs->r1);
printf(" r2: %016lx ", regs->r2);
printf(" r3: %016lx\n", regs->r3);
printf(" r4: %016lx ", regs->r4);
printf(" r5: %016lx\n", regs->r5);
printf(" r6: %016lx ", regs->r6);
printf(" r7: %016lx\n", regs->r7);
printf(" r8: %016lx ", regs->r8);
printf(" r9: %016lx\n", regs->r9);
printf(" r10: %016lx ", regs->r10);
printf(" r11: %016lx\n", regs->r11);
printf(" sp: %016lx ", regs->r12);
printf(" tp: %016lx\n", regs->r13);
printf(" r14: %016lx ", regs->r14);
printf(" r15: %016lx\n", regs->r15);
printf(" r16: %016lx ", regs->r16);
printf(" r17: %016lx\n", regs->r17);
printf(" r18: %016lx ", regs->r18);
printf(" r19: %016lx\n", regs->r19);
printf(" r20: %016lx ", regs->r20);
printf(" r21: %016lx\n", regs->r21);
printf(" r22: %016lx ", regs->r22);
printf(" r23: %016lx\n", regs->r23);
printf(" r24: %016lx ", regs->r24);
printf(" r25: %016lx\n", regs->r25);
printf(" r26: %016lx ", regs->r26);
printf(" r27: %016lx\n", regs->r27);
printf(" r28: %016lx ", regs->r28);
printf(" r29: %016lx\n", regs->r29);
printf(" r30: %016lx ", regs->r30);
printf(" r31: %016lx\n", regs->r31);
printf("\n itr: P rid va pa ps ed pl "
"ar a d ma key\n");
for (i = 0; i < 8; i++) {
ps_val = er->itrs[i].itir >> ITIR_PS_SHIFT & ITIR_PS_MASK;
ma_val = er->itrs[i].pte >> PTE_MA_SHIFT & PTE_MA_MASK;
pa = (er->itrs[i].pte >> PTE_PPN_SHIFT & PTE_PPN_MASK) <<
PTE_PPN_SHIFT;
pa = (pa >> ps_val) << ps_val;
printf(" [%d] %ld %06lx %016lx %013lx %02x %s %ld %ld %ld %ld "
"%ld %d %s %06lx\n", i,
er->itrs[i].pte >> PTE_P_SHIFT & PTE_P_MASK,
er->itrs[i].rid >> RR_RID_SHIFT & RR_RID_MASK,
er->itrs[i].vadr, pa, ps_val,
((ps_val >= ITIR_PS_MIN && ps_val <= ITIR_PS_MAX) ?
ps[ps_val - ITIR_PS_MIN] : " "),
er->itrs[i].pte >> PTE_ED_SHIFT & PTE_ED_MASK,
er->itrs[i].pte >> PTE_PL_SHIFT & PTE_PL_MASK,
er->itrs[i].pte >> PTE_AR_SHIFT & PTE_AR_MASK,
er->itrs[i].pte >> PTE_A_SHIFT & PTE_A_MASK,
er->itrs[i].pte >> PTE_D_SHIFT & PTE_D_MASK,
ma_val, ma[ma_val],
er->itrs[i].itir >> ITIR_KEY_SHIFT & ITIR_KEY_MASK);
}
printf("\n dtr: P rid va pa ps ed pl "
"ar a d ma key\n");
for (i = 0; i < 8; i++) {
ps_val = er->dtrs[i].itir >> ITIR_PS_SHIFT & ITIR_PS_MASK;
ma_val = er->dtrs[i].pte >> PTE_MA_SHIFT & PTE_MA_MASK;
pa = (er->dtrs[i].pte >> PTE_PPN_SHIFT & PTE_PPN_MASK) <<
PTE_PPN_SHIFT;
pa = (pa >> ps_val) << ps_val;
printf(" [%d] %ld %06lx %016lx %013lx %02x %s %ld %ld %ld %ld "
"%ld %d %s %06lx\n", i,
er->dtrs[i].pte >> PTE_P_SHIFT & PTE_P_MASK,
er->dtrs[i].rid >> RR_RID_SHIFT & RR_RID_MASK,
er->dtrs[i].vadr, pa, ps_val,
((ps_val >= ITIR_PS_MIN && ps_val <= ITIR_PS_MAX) ?
ps[ps_val - ITIR_PS_MIN] : " "),
er->dtrs[i].pte >> PTE_ED_SHIFT & PTE_ED_MASK,
er->dtrs[i].pte >> PTE_PL_SHIFT & PTE_PL_MASK,
er->dtrs[i].pte >> PTE_AR_SHIFT & PTE_AR_MASK,
er->dtrs[i].pte >> PTE_A_SHIFT & PTE_A_MASK,
er->dtrs[i].pte >> PTE_D_SHIFT & PTE_D_MASK,
ma_val, ma[ma_val],
er->dtrs[i].itir >> ITIR_KEY_SHIFT & ITIR_KEY_MASK);
}
}
#endif
#ifndef NO_TRANSLATION
void *map_page(vcpu_guest_context_t *ctx, int vcpu, size_t virt)
{
static unsigned long previous_mfn = 0;
static void *mapped = NULL;
unsigned long mfn = xc_translate_foreign_address(xc_handle, domid, vcpu, virt);
unsigned long offset = virt & ~XC_PAGE_MASK;
if (mapped && mfn == previous_mfn)
goto out;
if (mapped)
munmap(mapped, XC_PAGE_SIZE);
previous_mfn = mfn;
mapped = xc_map_foreign_range(xc_handle, domid, XC_PAGE_SIZE, PROT_READ, mfn);
if (mapped == NULL) {
fprintf(stderr, "failed to map page.\n");
exit(-1);
}
out:
return (void *)(mapped + offset);
}
void print_stack(vcpu_guest_context_t *ctx, int vcpu)
{
struct cpu_user_regs *regs = &ctx->user_regs;
size_t stack = STACK_POINTER(regs);
size_t stack_limit = (STACK_POINTER(regs) & XC_PAGE_MASK) + XC_PAGE_SIZE;
size_t frame;
size_t instr;
size_t *p;
int i;
printf("\n");
printf("Stack:\n");
for (i=1; i<STACK_ROWS+1 && stack < stack_limit; i++) {
while(stack < stack_limit && stack < STACK_POINTER(regs) + i*STACK_COLS*sizeof(stack)) {
p = map_page(ctx, vcpu, stack);
printf(" " FMT_SIZE_T, *p);
stack += sizeof(stack);
}
printf("\n");
}
printf("\n");
printf("Code:\n");
instr = INSTR_POINTER(regs) - 21;
for(i=0; i<32; i++) {
unsigned char *c = map_page(ctx, vcpu, instr+i);
if (instr+i == INSTR_POINTER(regs))
printf("<%02x> ", *c);
else
printf("%02x ", *c);
}
printf("\n");
printf("\n");
if(stack_trace)
printf("Stack Trace:\n");
else
printf("Call Trace:\n");
printf("%c [<" FMT_SIZE_T ">] ", stack_trace ? '*' : ' ', INSTR_POINTER(regs));
print_symbol(INSTR_POINTER(regs));
printf(" <--\n");
if (frame_ptrs) {
stack = STACK_POINTER(regs);
frame = FRAME_POINTER(regs);
while(frame && stack < stack_limit) {
if (stack_trace) {
while (stack < frame) {
p = map_page(ctx, vcpu, stack);
printf("| " FMT_SIZE_T " ", *p);
printf("\n");
stack += sizeof(*p);
}
} else {
stack = frame;
}
p = map_page(ctx, vcpu, stack);
frame = *p;
if (stack_trace)
printf("|-- " FMT_SIZE_T "\n", *p);
stack += sizeof(*p);
if (frame) {
p = map_page(ctx, vcpu, stack);
printf("%c [<" FMT_SIZE_T ">] ", stack_trace ? '|' : ' ', *p);
print_symbol(*p);
printf("\n");
stack += sizeof(*p);
}
}
} else {
stack = STACK_POINTER(regs);
while(stack < stack_limit) {
p = map_page(ctx, vcpu, stack);
if (is_kernel_text(*p)) {
printf(" [<" FMT_SIZE_T ">] ", *p);
print_symbol(*p);
printf("\n");
} else if (stack_trace) {
printf(" " FMT_SIZE_T "\n", *p);
}
stack += sizeof(*p);
}
}
}
#else
#define print_stack(ctx, vcpu)
#endif
void dump_ctx(int vcpu)
{
int ret;
vcpu_guest_context_t ctx;
xc_handle = xc_interface_open(); /* for accessing control interface */
ret = xc_domain_pause(xc_handle, domid);
if (ret < 0) {
perror("xc_domain_pause");
exit(-1);
}
ret = xc_vcpu_getcontext(xc_handle, domid, vcpu, &ctx);
if (ret < 0) {
xc_domain_unpause(xc_handle, domid);
perror("xc_vcpu_getcontext");
exit(-1);
}
print_ctx(&ctx);
if (is_kernel_text(INSTR_POINTER((&ctx.user_regs))))
print_stack(&ctx, vcpu);
ret = xc_domain_unpause(xc_handle, domid);
if (ret < 0) {
perror("xc_domain_unpause");
exit(-1);
}
xc_interface_close(xc_handle);
if (ret < 0) {
perror("xc_interface_close");
exit(-1);
}
}
void usage(void)
{
printf("usage:\n\n");
printf(" xenctx [options] <DOMAIN> [VCPU]\n\n");
printf("options:\n");
printf(" -f, --frame-pointers\n");
printf(" assume the kernel was compiled with\n");
printf(" frame pointers.\n");
printf(" -s SYMTAB, --symbol-table=SYMTAB\n");
printf(" read symbol table from SYMTAB.\n");
printf(" --stack-trace print a complete stack trace.\n");
}
int main(int argc, char **argv)
{
int ch;
const char *sopts = "fs:h";
const struct option lopts[] = {
{"stack-trace", 0, NULL, 'S'},
{"symbol-table", 1, NULL, 's'},
{"frame-pointers", 0, NULL, 'f'},
{"help", 0, NULL, 'h'},
{0, 0, 0, 0}
};
const char *symbol_table = NULL;
int vcpu = 0;
while ((ch = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
switch(ch) {
case 'f':
frame_ptrs = 1;
break;
case 's':
symbol_table = optarg;
break;
case 'S':
stack_trace = 1;
break;
case 'h':
usage();
exit(-1);
case '?':
fprintf(stderr, "%s --help for more options\n", argv[0]);
exit(-1);
}
}
argv += optind; argc -= optind;
if (argc < 1 || argc > 2) {
printf("usage: xenctx [options] <domid> <optional vcpu>\n");
exit(-1);
}
domid = atoi(argv[0]);
if (domid==0) {
fprintf(stderr, "cannot trace dom0\n");
exit(-1);
}
if (argc == 2)
vcpu = atoi(argv[1]);
if (symbol_table)
read_symbol_table(symbol_table);
dump_ctx(vcpu);
return 0;
}
/*
* Local variables:
* mode: C
* c-set-style: "BSD"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/