1 /* src/vm/jit/stacktrace.c - machine independent stacktrace system
3 Copyright (C) 1996-2005, 2006, 2007 R. Grafl, A. Krall, C. Kruegel,
4 C. Oates, R. Obermaisser, M. Platter, M. Probst, S. Ring,
5 E. Steiner, C. Thalinger, D. Thuernbeck, P. Tomsich, C. Ullrich,
6 J. Wenninger, Institut f. Computersprachen - TU Wien
8 This file is part of CACAO.
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License as
12 published by the Free Software Foundation; either version 2, or (at
13 your option) any later version.
15 This program is distributed in the hope that it will be useful, but
16 WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
36 #include "mm/gc-common.h"
37 #include "mm/memory.h"
39 #include "vm/jit/stacktrace.h"
41 #include "vm/global.h" /* required here for native includes */
42 #include "native/jni.h"
43 #include "native/llni.h"
44 #include "native/include/java_lang_Throwable.h"
46 #if defined(WITH_CLASSPATH_GNU)
47 # include "native/include/java_lang_VMThrowable.h"
50 #if defined(ENABLE_THREADS)
51 # include "threads/native/threads.h"
53 # include "threads/none/threads.h"
56 #include "toolbox/logging.h"
58 #include "vm/builtin.h"
59 #include "vm/cycles-stats.h"
60 #include "vm/exceptions.h"
61 #include "vm/stringlocal.h"
64 #include "vm/jit/asmpart.h"
65 #include "vm/jit/codegen-common.h"
66 #include "vm/jit/methodheader.h"
68 #include "vmcore/class.h"
69 #include "vmcore/loader.h"
70 #include "vmcore/options.h"
73 /* global variables ***********************************************************/
74 #if !defined(ENABLE_THREADS)
75 stackframeinfo *_no_threads_stackframeinfo = NULL;
78 CYCLES_STATS_DECLARE(stacktrace_overhead ,100,1)
79 CYCLES_STATS_DECLARE(stacktrace_fillInStackTrace,40,5000)
80 CYCLES_STATS_DECLARE(stacktrace_getClassContext ,40,5000)
81 CYCLES_STATS_DECLARE(stacktrace_getCurrentClass ,40,5000)
82 CYCLES_STATS_DECLARE(stacktrace_getStack ,40,10000)
85 /* stacktrace_create_stackframeinfo ********************************************
87 Creates an stackframe info structure for inline code in the
90 *******************************************************************************/
92 #if defined(ENABLE_INTRP)
93 void stacktrace_create_stackframeinfo(stackframeinfo *sfi, u1 *pv, u1 *sp,
96 stackframeinfo **psfi;
100 /* get current stackframe info pointer */
102 psfi = &STACKFRAMEINFO;
104 /* if we don't have pv handy */
107 #if defined(ENABLE_INTRP)
109 pv = codegen_get_pv_from_pc(ra);
113 #if defined(ENABLE_JIT)
114 pv = md_codegen_get_pv_from_pc(ra);
119 /* get codeinfo pointer from data segment */
121 code = *((codeinfo **) (pv + CodeinfoPointer));
123 /* For asm_vm_call_method the codeinfo pointer is NULL. */
125 m = (code == NULL) ? NULL : code->m;
127 /* fill new stackframe info structure */
135 /* xpc is the same as ra, but is required in stacktrace_create */
139 /* store new stackframe info pointer */
143 #endif /* defined(ENABLE_INTRP) */
146 /* stacktrace_create_extern_stackframeinfo *************************************
148 Creates an stackframe info structure for an extern exception
149 (hardware or assembler).
151 *******************************************************************************/
153 void stacktrace_create_extern_stackframeinfo(stackframeinfo *sfi, u1 *pv,
154 u1 *sp, u1 *ra, u1 *xpc)
156 stackframeinfo **psfi;
157 #if !defined(__I386__) && !defined(__X86_64__) && !defined(__S390__) && !defined(__M68K__)
160 #if defined(ENABLE_JIT)
164 /* get current stackframe info pointer */
166 psfi = &STACKFRAMEINFO;
168 /* sometimes we don't have pv handy (e.g. in asmpart.S:
169 L_asm_call_jit_compiler_exception or in the interpreter). */
172 #if defined(ENABLE_INTRP)
174 pv = codegen_get_pv_from_pc(ra);
178 #if defined(ENABLE_JIT)
179 # if defined(__SPARC_64__)
180 pv = md_get_pv_from_stackframe(sp);
182 pv = md_codegen_get_pv_from_pc(ra);
188 #if defined(ENABLE_JIT)
189 # if defined(ENABLE_INTRP)
190 /* When using the interpreter, we pass RA to the function. */
194 # if defined(__I386__) || defined(__X86_64__) || defined(__S390__) || defined(__M68K__)
195 /* On i386 and x86_64 we always have to get the return address
197 /* m68k has return address on stack always */
198 /* On S390 we use REG_RA as REG_ITMP3, so we have always to get
199 the RA from stack. */
201 framesize = *((u4 *) (pv + FrameSize));
203 ra = md_stacktrace_get_returnaddress(sp, framesize);
205 /* If the method is a non-leaf function, we need to get the return
206 address from the stack. For leaf functions the return address
207 is set correctly. This makes the assembler and the signal
208 handler code simpler. */
210 isleafmethod = *((s4 *) (pv + IsLeaf));
213 framesize = *((u4 *) (pv + FrameSize));
215 ra = md_stacktrace_get_returnaddress(sp, framesize);
218 # if defined(ENABLE_INTRP)
221 #endif /* defined(ENABLE_JIT) */
223 /* fill new stackframe info structure */
232 /* store new stackframe info pointer */
236 /* set the native world flag for the current thread */
237 /* ATTENTION: This flag tells the GC how to treat this thread in case of
238 a collection. Set this flag _after_ a valid stackframe info was set. */
240 THREAD_NATIVEWORLD_ENTER;
244 /* stacktrace_create_native_stackframeinfo *************************************
246 Creates a stackframe info structure for a native stub.
248 *******************************************************************************/
250 void stacktrace_create_native_stackframeinfo(stackframeinfo *sfi, u1 *pv,
253 stackframeinfo **psfi;
257 /* get codeinfo pointer from data segment */
259 code = *((codeinfo **) (pv + CodeinfoPointer));
261 /* For asm_vm_call_method the codeinfo pointer is NULL. */
263 m = (code == NULL) ? NULL : code->m;
265 /* get current stackframe info pointer */
267 psfi = &STACKFRAMEINFO;
269 /* fill new stackframe info structure */
278 /* store new stackframe info pointer */
282 /* set the native world flag for the current thread */
283 /* ATTENTION: This flag tells the GC how to treat this thread in case of
284 a collection. Set this flag _after_ a valid stackframe info was set. */
286 THREAD_NATIVEWORLD_ENTER;
290 /* stacktrace_remove_stackframeinfo ********************************************
292 Remove the topmost stackframeinfo in the current thread.
294 *******************************************************************************/
296 void stacktrace_remove_stackframeinfo(stackframeinfo *sfi)
298 stackframeinfo **psfi;
300 /* clear the native world flag for the current thread */
301 /* ATTENTION: Clear this flag _before_ removing the stackframe info */
303 THREAD_NATIVEWORLD_EXIT;
305 /* get current stackframe info pointer */
307 psfi = &STACKFRAMEINFO;
309 /* restore the old pointer */
315 /* stacktrace_add_entry ********************************************************
317 Adds a new entry to the stacktrace buffer.
319 *******************************************************************************/
321 static stacktracebuffer *stacktrace_add_entry(stacktracebuffer *stb,
322 methodinfo *m, u2 line)
324 stacktrace_entry *ste;
328 /* check if we already reached the buffer capacity */
330 if (stb->used >= stb->capacity) {
331 /* calculate size of stacktracebuffer */
333 stb_size_old = sizeof(stacktracebuffer) +
334 sizeof(stacktrace_entry) * stb->capacity -
335 sizeof(stacktrace_entry) * STACKTRACE_CAPACITY_DEFAULT;
337 stb_size_new = stb_size_old +
338 sizeof(stacktrace_entry) * STACKTRACE_CAPACITY_INCREMENT;
340 /* reallocate new memory */
342 stb = DMREALLOC(stb, u1, stb_size_old, stb_size_new);
344 /* set new buffer capacity */
346 stb->capacity = stb->capacity + STACKTRACE_CAPACITY_INCREMENT;
349 /* insert the current entry */
351 ste = &(stb->entries[stb->used]);
354 ste->linenumber = line;
356 /* increase entries used count */
364 /* stacktrace_add_method *******************************************************
366 Add stacktrace entries[1] for the given method to the stacktrace buffer.
369 stb.........stacktracebuffer to fill
370 m...........method for which entries should be created
371 pv..........pv of method
372 pc..........position of program counter within the method's code
375 stacktracebuffer after possible reallocation.
377 [1] In case of inlined methods there may be more than one stacktrace
378 entry for a codegen-level method. (see doc/inlining_stacktrace.txt)
380 *******************************************************************************/
382 static stacktracebuffer *stacktrace_add_method(stacktracebuffer *stb,
383 methodinfo *m, u1 *pv, u1 *pc)
385 codeinfo *code; /* compiled realization of method */
388 /* find the realization of the method the pc is in */
390 code = *((codeinfo **) (pv + CodeinfoPointer));
392 /* search the line number table */
394 linenumber = dseg_get_linenumber_from_pc(&m, pv, pc);
396 /* now add a new entry to the staktrace */
398 stb = stacktrace_add_entry(stb, m, linenumber);
404 /* stacktrace_create ***********************************************************
406 Generates a stacktrace from the thread passed into a
407 stacktracebuffer. The stacktracebuffer is allocated on the
410 NOTE: The first element in the stackframe chain must always be a
411 native stackframeinfo (e.g. VMThrowable.fillInStackTrace() is
415 pointer to the stacktracebuffer, or
416 NULL if there is no stacktrace available for the
419 *******************************************************************************/
421 stacktracebuffer *stacktrace_create(stackframeinfo *sfi)
423 stacktracebuffer *stb;
432 /* prevent compiler warnings */
438 /* create a stacktracebuffer in dump memory */
440 stb = DNEW(stacktracebuffer);
442 stb->capacity = STACKTRACE_CAPACITY_DEFAULT;
445 #define PRINTMETHODS 0
448 printf("\n\nfillInStackTrace start:\n");
452 /* Loop while we have a method pointer (asm_calljavafunction has
453 NULL) or there is a stackframeinfo in the chain. */
457 while ((m != NULL) || (sfi != NULL)) {
458 /* m == NULL should only happen for the first time and inline
459 stackframe infos, like from the exception stubs or the
463 /* for native stub stackframe infos, pv is always NULL */
465 if (sfi->pv == NULL) {
466 /* get methodinfo, sp and ra from the current stackframe info */
469 sp = sfi->sp; /* sp of parent Java function */
473 stb = stacktrace_add_entry(stb, m, 0);
476 printf("ra=%p sp=%p, ", ra, sp);
478 printf(": native stub\n");
481 /* This is an native stub stackframe info, so we can
482 get the parent pv from the return address
485 #if defined(ENABLE_INTRP)
487 pv = codegen_get_pv_from_pc(ra);
491 #if defined(ENABLE_JIT)
492 pv = md_codegen_get_pv_from_pc(ra);
496 /* get methodinfo pointer from parent data segment */
498 code = *((codeinfo **) (pv + CodeinfoPointer));
500 /* For asm_vm_call_method the codeinfo pointer is
503 m = (code == NULL) ? NULL : code->m;
506 /* Inline stackframe infos are special: they have a
507 xpc of the actual exception position and the return
508 address saved since an inline stackframe info can
509 also be in a leaf method (no return address saved
510 on stack!!!). ATTENTION: This one is also for
511 hardware exceptions!!! */
513 /* get methodinfo, sp and ra from the current stackframe info */
515 m = sfi->method; /* m == NULL */
516 pv = sfi->pv; /* pv of parent Java function */
517 sp = sfi->sp; /* sp of parent Java function */
518 ra = sfi->ra; /* ra of parent Java function */
519 xpc = sfi->xpc; /* actual exception position */
522 printf("ra=%p sp=%p, ", ra, sp);
523 printf("NULL: inline stub\n");
527 /* get methodinfo from current Java method */
529 code = *((codeinfo **) (pv + CodeinfoPointer));
531 /* For asm_vm_call_method the codeinfo pointer is
534 m = (code == NULL) ? NULL : code->m;
536 /* if m == NULL, this is a asm_calljavafunction call */
540 printf("ra=%p sp=%p, ", ra, sp);
542 printf(": inline stub parent");
546 #if defined(ENABLE_INTRP)
550 /* add the method to the stacktrace */
552 stb = stacktrace_add_method(stb, m, pv, (u1 *) ((ptrint) xpc));
554 /* get the current stack frame size */
556 framesize = *((u4 *) (pv + FrameSize));
559 printf(", framesize=%d\n", framesize);
563 /* Set stack pointer to stackframe of parent Java
564 function of the current Java function. */
566 #if defined(__I386__) || defined (__X86_64__) || defined (__M68K__)
567 sp += framesize + SIZEOF_VOID_P;
568 #elif defined(__SPARC_64__)
569 sp = md_get_framepointer(sp);
574 /* get data segment and methodinfo pointer from
577 #if defined(ENABLE_JIT)
578 pv = md_codegen_get_pv_from_pc(ra);
581 code = *((codeinfo **) (pv + CodeinfoPointer));
583 /* For asm_vm_call_method the codeinfo pointer is
586 m = (code == NULL) ? NULL : code->m;
588 #if defined(ENABLE_INTRP)
594 printf("ra=%p sp=%p, ", ra, sp);
595 printf("asm_calljavafunction\n");
601 /* get previous stackframeinfo in the chain */
607 printf("ra=%p sp=%p, ", ra, sp);
613 /* JIT method found, add it to the stacktrace (we subtract
614 1 from the return address since it points the the
615 instruction after call). */
617 stb = stacktrace_add_method(stb, m, pv, (u1 *) ((ptrint) ra) - 1);
619 /* get the current stack frame size */
621 framesize = *((u4 *) (pv + FrameSize));
624 printf(", framesize=%d\n", framesize);
628 /* get return address of current stack frame */
630 #if defined(ENABLE_JIT)
631 # if defined(ENABLE_INTRP)
633 ra = intrp_md_stacktrace_get_returnaddress(sp, framesize);
636 ra = md_stacktrace_get_returnaddress(sp, framesize);
638 ra = intrp_md_stacktrace_get_returnaddress(sp, framesize);
641 /* get data segment and methodinfo pointer from parent method */
643 #if defined(ENABLE_INTRP)
645 pv = codegen_get_pv_from_pc(ra);
649 #if defined(ENABLE_JIT)
650 # if defined(__SPARC_64__)
651 sp = md_get_framepointer(sp);
652 pv = md_get_pv_from_stackframe(sp);
654 pv = md_codegen_get_pv_from_pc(ra);
659 code = *((codeinfo **) (pv + CodeinfoPointer));
661 /* For asm_vm_call_method the codeinfo pointer is NULL. */
663 m = (code == NULL) ? NULL : code->m;
667 #if defined(ENABLE_INTRP)
669 sp = *(u1 **) (sp - framesize);
673 #if defined(__I386__) || defined (__X86_64__) || defined (__M68K__)
674 sp += framesize + SIZEOF_VOID_P;
675 #elif defined(__SPARC_64__)
676 /* already has the new sp */
684 /* return the stacktracebuffer */
693 /* stacktrace_fillInStackTrace *************************************************
695 Generate a stacktrace from the current thread for
696 java.lang.VMThrowable.fillInStackTrace.
698 *******************************************************************************/
700 stacktracecontainer *stacktrace_fillInStackTrace(void)
702 stacktracebuffer *stb;
703 stacktracecontainer *gcstc;
706 CYCLES_STATS_DECLARE_AND_START_WITH_OVERHEAD
708 /* mark start of dump memory area */
710 dumpsize = dump_size();
712 /* create a stacktrace from the current thread */
714 stb = stacktrace_create(STACKFRAMEINFO);
719 /* allocate memory from the GC heap and copy the stacktrace buffer */
720 /* ATTENTION: use stacktracecontainer for this and make it look like
723 gcstc_size = sizeof(stacktracebuffer) +
724 sizeof(stacktrace_entry) * stb->used -
725 sizeof(stacktrace_entry) * STACKTRACE_CAPACITY_DEFAULT;
726 gcstc = (stacktracecontainer *) builtin_newarray_byte(gcstc_size);
731 MCOPY(&(gcstc->stb), stb, u1, gcstc_size);
733 /* release dump memory */
735 dump_release(dumpsize);
737 CYCLES_STATS_END_WITH_OVERHEAD(stacktrace_fillInStackTrace,
742 dump_release(dumpsize);
744 CYCLES_STATS_END_WITH_OVERHEAD(stacktrace_fillInStackTrace,
751 /* stacktrace_getClassContext **************************************************
753 Creates a Class context array.
756 the array of java.lang.Class objects, or
757 NULL if an exception has been thrown
759 *******************************************************************************/
761 java_handle_objectarray_t *stacktrace_getClassContext(void)
763 stacktracebuffer *stb;
764 stacktrace_entry *ste;
765 java_handle_objectarray_t *oa;
769 CYCLES_STATS_DECLARE_AND_START
771 /* mark start of dump memory area */
773 dumpsize = dump_size();
775 /* create a stacktrace for the current thread */
777 stb = stacktrace_create(STACKFRAMEINFO);
782 /* calculate the size of the Class array */
784 for (i = 0, oalength = 0; i < stb->used; i++)
785 if (stb->entries[i].method != NULL)
788 /* The first entry corresponds to the method whose implementation */
789 /* calls stacktrace_getClassContext. We remove that entry. */
791 ste = &(stb->entries[0]);
795 /* allocate the Class array */
797 oa = builtin_anewarray(oalength, class_java_lang_Class);
801 /* fill the Class array from the stacktracebuffer */
803 for(i = 0; i < oalength; i++, ste++) {
804 if (ste->method == NULL) {
809 LLNI_array_direct(oa, i) = (java_object_t *) ste->method->class;
812 /* release dump memory */
814 dump_release(dumpsize);
816 CYCLES_STATS_END(stacktrace_getClassContext)
821 dump_release(dumpsize);
823 CYCLES_STATS_END(stacktrace_getClassContext)
829 /* stacktrace_getCurrentClass **************************************************
831 Find the current class by walking the stack trace.
833 Quote from the JNI documentation:
835 In the Java 2 Platform, FindClass locates the class loader
836 associated with the current native method. If the native code
837 belongs to a system class, no class loader will be
838 involved. Otherwise, the proper class loader will be invoked to
839 load and link the named class. When FindClass is called through the
840 Invocation Interface, there is no current native method or its
841 associated class loader. In that case, the result of
842 ClassLoader.getBaseClassLoader is used."
844 *******************************************************************************/
846 #if defined(ENABLE_JAVASE)
847 classinfo *stacktrace_getCurrentClass(void)
849 stacktracebuffer *stb;
850 stacktrace_entry *ste;
854 CYCLES_STATS_DECLARE_AND_START
856 /* mark start of dump memory area */
858 dumpsize = dump_size();
860 /* create a stacktrace for the current thread */
862 stb = stacktrace_create(STACKFRAMEINFO);
865 goto return_NULL; /* XXX exception: how to distinguish from normal NULL return? */
867 /* iterate over all stacktrace entries and find the first suitable
870 for (i = 0, ste = &(stb->entries[0]); i < stb->used; i++, ste++) {
876 if (m->class == class_java_security_PrivilegedAction)
879 if (m->class != NULL) {
880 dump_release(dumpsize);
882 CYCLES_STATS_END(stacktrace_getCurrentClass)
888 /* no Java method found on the stack */
891 dump_release(dumpsize);
893 CYCLES_STATS_END(stacktrace_getCurrentClass)
897 #endif /* ENABLE_JAVASE */
900 /* stacktrace_getStack *********************************************************
902 Create a 2-dimensional array for java.security.VMAccessControler.
906 NULL if an exception has been thrown
908 *******************************************************************************/
910 #if defined(ENABLE_JAVASE)
911 java_handle_objectarray_t *stacktrace_getStack(void)
913 stacktracebuffer *stb;
914 stacktrace_entry *ste;
915 java_handle_objectarray_t *oa;
916 java_handle_objectarray_t *classes;
917 java_handle_objectarray_t *methodnames;
919 java_handle_t *string;
922 CYCLES_STATS_DECLARE_AND_START
924 /* mark start of dump memory area */
926 dumpsize = dump_size();
928 /* create a stacktrace for the current thread */
930 stb = stacktrace_create(STACKFRAMEINFO);
935 /* get the first stacktrace entry */
937 ste = &(stb->entries[0]);
939 /* allocate all required arrays */
941 oa = builtin_anewarray(2, arrayclass_java_lang_Object);
946 classes = builtin_anewarray(stb->used, class_java_lang_Class);
951 methodnames = builtin_anewarray(stb->used, class_java_lang_String);
953 if (methodnames == NULL)
956 /* set up the 2-dimensional array */
958 LLNI_objectarray_element_set(oa, 0, classes);
959 LLNI_objectarray_element_set(oa, 1, methodnames);
961 /* iterate over all stacktrace entries */
963 for (i = 0, ste = &(stb->entries[0]); i < stb->used; i++, ste++) {
964 c = ste->method->class;
966 LLNI_array_direct(classes, i) = (java_object_t *) c;
968 string = javastring_new(ste->method->name);
973 LLNI_objectarray_element_set(methodnames, i, string);
976 /* return the 2-dimensional array */
978 dump_release(dumpsize);
980 CYCLES_STATS_END(stacktrace_getStack)
985 dump_release(dumpsize);
987 CYCLES_STATS_END(stacktrace_getStack)
991 #endif /* ENABLE_JAVASE */
994 /* stacktrace_print_trace_from_buffer ******************************************
996 Print the stacktrace of a given stacktracebuffer with CACAO intern
997 methods (no Java help). This method is used by
998 stacktrace_dump_trace and builtin_trace_exception.
1000 *******************************************************************************/
1002 void stacktrace_print_trace_from_buffer(stacktracebuffer *stb)
1004 stacktrace_entry *ste;
1008 ste = &(stb->entries[0]);
1010 for (i = 0; i < stb->used; i++, ste++) {
1014 utf_display_printable_ascii_classname(m->class->name);
1016 utf_display_printable_ascii(m->name);
1017 utf_display_printable_ascii(m->descriptor);
1019 if (m->flags & ACC_NATIVE) {
1020 puts("(Native Method)");
1024 utf_display_printable_ascii(m->class->sourcefile);
1025 printf(":%d)\n", (u4) ste->linenumber);
1029 /* just to be sure */
1035 /* stacktrace_print_trace ******************************************************
1037 Print the stacktrace of a given exception. More or less a wrapper
1038 to stacktrace_print_trace_from_buffer.
1040 *******************************************************************************/
1042 void stacktrace_print_trace(java_handle_t *xptr)
1044 java_lang_Throwable *t;
1045 #if defined(WITH_CLASSPATH_GNU)
1046 java_lang_VMThrowable *vmt;
1048 stacktracecontainer *stc;
1049 stacktracebuffer *stb;
1051 t = (java_lang_Throwable *) xptr;
1056 /* now print the stacktrace */
1058 #if defined(WITH_CLASSPATH_GNU)
1059 LLNI_field_get_ref(t, vmState, vmt);
1060 stc = (stacktracecontainer *) LLNI_field_direct(vmt, vmData);
1061 #elif defined(WITH_CLASSPATH_SUN) || defined(WITH_CLASSPATH_CLDC1_1)
1062 stc = (stacktracecontainer *) t->backtrace;
1064 # error unknown classpath configuration
1069 stacktrace_print_trace_from_buffer(stb);
1073 #if defined(ENABLE_CYCLES_STATS)
1074 void stacktrace_print_cycles_stats(FILE *file)
1076 CYCLES_STATS_PRINT_OVERHEAD(stacktrace_overhead,file);
1077 CYCLES_STATS_PRINT(stacktrace_fillInStackTrace,file);
1078 CYCLES_STATS_PRINT(stacktrace_getClassContext ,file);
1079 CYCLES_STATS_PRINT(stacktrace_getCurrentClass ,file);
1080 CYCLES_STATS_PRINT(stacktrace_getStack ,file);
1086 * These are local overrides for various environment variables in Emacs.
1087 * Please do not remove this and leave it at the end of the file, where
1088 * Emacs will automagically detect them.
1089 * ---------------------------------------------------------------------
1092 * indent-tabs-mode: t
1096 * vim:noexpandtab:sw=4:ts=4: