diff options
| author | Bjoern Brandenburg <bbb@mpi-sws.org> | 2015-08-09 07:18:44 -0400 |
|---|---|---|
| committer | Bjoern Brandenburg <bbb@mpi-sws.org> | 2017-05-26 17:12:20 -0400 |
| commit | 101432981f6934bc11d917b6cd79bb4ac912edad (patch) | |
| tree | eba1dee5a605544ff7775c0b17984cdd8a2b3440 /arch/x86 | |
| parent | b06b1335d1d4481b893a8683406b52ed28e228fd (diff) | |
Feather-Trace: add x86 binary rewriting implementation
This patch adds the x86-specific implementation of Feather-Trace
triggers that works by rewriting jump instructions.
Diffstat (limited to 'arch/x86')
| -rw-r--r-- | arch/x86/Kconfig | 2 | ||||
| -rw-r--r-- | arch/x86/include/asm/feather_trace.h | 17 | ||||
| -rw-r--r-- | arch/x86/include/asm/feather_trace_32.h | 115 | ||||
| -rw-r--r-- | arch/x86/include/asm/feather_trace_64.h | 124 | ||||
| -rw-r--r-- | arch/x86/kernel/Makefile | 2 | ||||
| -rw-r--r-- | arch/x86/kernel/ft_event.c | 118 |
6 files changed, 377 insertions, 1 deletions
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index d4c29c8046dd..a70cdaeafe88 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig | |||
| @@ -2776,6 +2776,6 @@ source "arch/x86/kvm/Kconfig" | |||
| 2776 | source "lib/Kconfig" | 2776 | source "lib/Kconfig" |
| 2777 | 2777 | ||
| 2778 | config ARCH_HAS_FEATHER_TRACE | 2778 | config ARCH_HAS_FEATHER_TRACE |
| 2779 | def_bool n | 2779 | def_bool y |
| 2780 | 2780 | ||
| 2781 | source "litmus/Kconfig" | 2781 | source "litmus/Kconfig" |
diff --git a/arch/x86/include/asm/feather_trace.h b/arch/x86/include/asm/feather_trace.h new file mode 100644 index 000000000000..4fd31633405d --- /dev/null +++ b/arch/x86/include/asm/feather_trace.h | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | #ifndef _ARCH_FEATHER_TRACE_H | ||
| 2 | #define _ARCH_FEATHER_TRACE_H | ||
| 3 | |||
| 4 | #include <asm/msr.h> | ||
| 5 | |||
| 6 | static inline unsigned long long ft_timestamp(void) | ||
| 7 | { | ||
| 8 | return __native_read_tsc(); | ||
| 9 | } | ||
| 10 | |||
| 11 | #ifdef CONFIG_X86_32 | ||
| 12 | #include "feather_trace_32.h" | ||
| 13 | #else | ||
| 14 | #include "feather_trace_64.h" | ||
| 15 | #endif | ||
| 16 | |||
| 17 | #endif | ||
diff --git a/arch/x86/include/asm/feather_trace_32.h b/arch/x86/include/asm/feather_trace_32.h new file mode 100644 index 000000000000..75e81a9f9382 --- /dev/null +++ b/arch/x86/include/asm/feather_trace_32.h | |||
| @@ -0,0 +1,115 @@ | |||
| 1 | /* Copyright (c) 2007-2012 Björn Brandenburg, <bbb@mpi-sws.org> | ||
| 2 | * | ||
| 3 | * Permission is hereby granted, free of charge, to any person obtaining | ||
| 4 | * a copy of this software and associated documentation files (the | ||
| 5 | * "Software"), to deal in the Software without restriction, including | ||
| 6 | * without limitation the rights to use, copy, modify, merge, publish, | ||
| 7 | * distribute, sublicense, and/or sell copies of the Software, and to | ||
| 8 | * permit persons to whom the Software is furnished to do so, subject to | ||
| 9 | * the following conditions: | ||
| 10 | * | ||
| 11 | * The above copyright notice and this permission notice shall be | ||
| 12 | * included in all copies or substantial portions of the Software. | ||
| 13 | * | ||
| 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
| 15 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
| 16 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| 17 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | ||
| 18 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | ||
| 19 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||
| 20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| 21 | * SOFTWARE. | ||
| 22 | */ | ||
| 23 | |||
| 24 | /* Do not directly include this file. Include feather_trace.h instead */ | ||
| 25 | |||
| 26 | #define feather_callback __attribute__((regparm(3))) __attribute__((used)) | ||
| 27 | |||
| 28 | /* | ||
| 29 | * Make the compiler reload any register that is not saved in a cdecl function | ||
| 30 | * call (minus the registers that we explicitly clobber as output registers). | ||
| 31 | */ | ||
| 32 | #define __FT_CLOBBER_LIST0 "memory", "cc", "eax", "edx", "ecx" | ||
| 33 | #define __FT_CLOBBER_LIST1 "memory", "cc", "eax", "ecx" | ||
| 34 | #define __FT_CLOBBER_LIST2 "memory", "cc", "eax" | ||
| 35 | #define __FT_CLOBBER_LIST3 "memory", "cc", "eax" | ||
| 36 | |||
| 37 | #define __FT_TMP1(x) "=d" (x) | ||
| 38 | #define __FT_ARG1(x) "0" ((long) (x)) | ||
| 39 | #define __FT_TMP2(x) "=c" (x) | ||
| 40 | #define __FT_ARG2(x) "1" ((long) (x)) | ||
| 41 | |||
| 42 | #define __FT_ARG3(x) "r" ((long) (x)) | ||
| 43 | |||
| 44 | #define ft_event(id, callback) \ | ||
| 45 | __asm__ __volatile__( \ | ||
| 46 | "1: jmp 2f \n\t" \ | ||
| 47 | " call " #callback " \n\t" \ | ||
| 48 | ".section __event_table, \"aw\" \n\t" \ | ||
| 49 | ".long " #id ", 0, 1b, 2f \n\t" \ | ||
| 50 | ".previous \n\t" \ | ||
| 51 | "2: \n\t" \ | ||
| 52 | : : : __FT_CLOBBER_LIST0) | ||
| 53 | |||
| 54 | #define ft_event0(id, callback) \ | ||
| 55 | __asm__ __volatile__( \ | ||
| 56 | "1: jmp 2f \n\t" \ | ||
| 57 | " movl $" #id ", %%eax \n\t" \ | ||
| 58 | " call " #callback " \n\t" \ | ||
| 59 | ".section __event_table, \"aw\" \n\t" \ | ||
| 60 | ".long " #id ", 0, 1b, 2f \n\t" \ | ||
| 61 | ".previous \n\t" \ | ||
| 62 | "2: \n\t" \ | ||
| 63 | : : : __FT_CLOBBER_LIST0) | ||
| 64 | |||
| 65 | #define ft_event1(id, callback, param) \ | ||
| 66 | do { \ | ||
| 67 | long __ft_tmp1; \ | ||
| 68 | __asm__ __volatile__( \ | ||
| 69 | "1: jmp 2f \n\t" \ | ||
| 70 | " movl $" #id ", %%eax \n\t" \ | ||
| 71 | " call " #callback " \n\t" \ | ||
| 72 | ".section __event_table, \"aw\" \n\t" \ | ||
| 73 | ".long " #id ", 0, 1b, 2f \n\t" \ | ||
| 74 | ".previous \n\t" \ | ||
| 75 | "2: \n\t" \ | ||
| 76 | : __FT_TMP1(__ft_tmp1) \ | ||
| 77 | : __FT_ARG1(param) \ | ||
| 78 | : __FT_CLOBBER_LIST1); \ | ||
| 79 | } while (0); | ||
| 80 | |||
| 81 | #define ft_event2(id, callback, param, param2) \ | ||
| 82 | do { \ | ||
| 83 | long __ft_tmp1, __ft_tmp2; \ | ||
| 84 | __asm__ __volatile__( \ | ||
| 85 | "1: jmp 2f \n\t" \ | ||
| 86 | " movl $" #id ", %%eax \n\t" \ | ||
| 87 | " call " #callback " \n\t" \ | ||
| 88 | ".section __event_table, \"aw\" \n\t" \ | ||
| 89 | ".long " #id ", 0, 1b, 2f \n\t" \ | ||
| 90 | ".previous \n\t" \ | ||
| 91 | "2: \n\t" \ | ||
| 92 | : __FT_TMP1(__ft_tmp1), __FT_TMP2(__ft_tmp2) \ | ||
| 93 | : __FT_ARG1(param), __FT_ARG2(param2) \ | ||
| 94 | : __FT_CLOBBER_LIST2); \ | ||
| 95 | } while (0); | ||
| 96 | |||
| 97 | |||
| 98 | #define ft_event3(id, callback, param, param2, param3) \ | ||
| 99 | do { \ | ||
| 100 | long __ft_tmp1, __ft_tmp2; \ | ||
| 101 | __asm__ __volatile__( \ | ||
| 102 | "1: jmp 2f \n\t" \ | ||
| 103 | " subl $4, %%esp \n\t" \ | ||
| 104 | " movl $" #id ", %%eax \n\t" \ | ||
| 105 | " movl %2, (%%esp) \n\t" \ | ||
| 106 | " call " #callback " \n\t" \ | ||
| 107 | " addl $4, %%esp \n\t" \ | ||
| 108 | ".section __event_table, \"aw\" \n\t" \ | ||
| 109 | ".long " #id ", 0, 1b, 2f \n\t" \ | ||
| 110 | ".previous \n\t" \ | ||
| 111 | "2: \n\t" \ | ||
| 112 | : __FT_TMP1(__ft_tmp1), __FT_TMP2(__ft_tmp2) \ | ||
| 113 | : __FT_ARG1(param), __FT_ARG2(param2), __FT_ARG3(param3) \ | ||
| 114 | : __FT_CLOBBER_LIST3); \ | ||
| 115 | } while (0); | ||
diff --git a/arch/x86/include/asm/feather_trace_64.h b/arch/x86/include/asm/feather_trace_64.h new file mode 100644 index 000000000000..5ce49e2eebba --- /dev/null +++ b/arch/x86/include/asm/feather_trace_64.h | |||
| @@ -0,0 +1,124 @@ | |||
| 1 | /* Copyright (c) 2010 Andrea Bastoni, <bastoni@cs.unc.edu> | ||
| 2 | * Copyright (c) 2012 Björn Brandenburg, <bbb@mpi-sws.org> | ||
| 3 | * | ||
| 4 | * Permission is hereby granted, free of charge, to any person obtaining | ||
| 5 | * a copy of this software and associated documentation files (the | ||
| 6 | * "Software"), to deal in the Software without restriction, including | ||
| 7 | * without limitation the rights to use, copy, modify, merge, publish, | ||
| 8 | * distribute, sublicense, and/or sell copies of the Software, and to | ||
| 9 | * permit persons to whom the Software is furnished to do so, subject to | ||
| 10 | * the following conditions: | ||
| 11 | * | ||
| 12 | * The above copyright notice and this permission notice shall be | ||
| 13 | * included in all copies or substantial portions of the Software. | ||
| 14 | * | ||
| 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
| 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
| 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | ||
| 19 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | ||
| 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||
| 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| 22 | * SOFTWARE. | ||
| 23 | */ | ||
| 24 | |||
| 25 | /* Do not directly include this file. Include feather_trace.h instead */ | ||
| 26 | |||
| 27 | /* regparm is the default on x86_64 */ | ||
| 28 | #define feather_callback __attribute__((used)) | ||
| 29 | |||
| 30 | #define __FT_EVENT_TABLE(id,from,to) \ | ||
| 31 | ".section __event_table, \"aw\"\n\t" \ | ||
| 32 | ".balign 8\n\t" \ | ||
| 33 | ".quad " #id ", 0, " #from ", " #to " \n\t" \ | ||
| 34 | ".previous \n\t" | ||
| 35 | |||
| 36 | /* | ||
| 37 | * x86_64 caller only owns rbp, rbx, r12-r15; | ||
| 38 | * the callee can freely modify the others. | ||
| 39 | */ | ||
| 40 | #define __FT_CLOBBER_LIST0 "memory", "cc", "rdi", "rsi", "rdx", "rcx", \ | ||
| 41 | "r8", "r9", "r10", "r11", "rax" | ||
| 42 | |||
| 43 | #define __FT_CLOBBER_LIST1 "memory", "cc", "rdi", "rdx", "rcx", \ | ||
| 44 | "r8", "r9", "r10", "r11", "rax" | ||
| 45 | |||
| 46 | #define __FT_CLOBBER_LIST2 "memory", "cc", "rdi", "rcx", \ | ||
| 47 | "r8", "r9", "r10", "r11", "rax" | ||
| 48 | |||
| 49 | #define __FT_CLOBBER_LIST3 "memory", "cc", "rdi", \ | ||
| 50 | "r8", "r9", "r10", "r11", "rax" | ||
| 51 | |||
| 52 | /* The registers RDI, RSI, RDX, RCX, R8 and R9 are used for integer and pointer | ||
| 53 | * arguments. */ | ||
| 54 | |||
| 55 | /* RSI */ | ||
| 56 | #define __FT_TMP1(x) "=S" (x) | ||
| 57 | #define __FT_ARG1(x) "0" ((long) (x)) | ||
| 58 | |||
| 59 | /* RDX */ | ||
| 60 | #define __FT_TMP2(x) "=d" (x) | ||
| 61 | #define __FT_ARG2(x) "1" ((long) (x)) | ||
| 62 | |||
| 63 | /* RCX */ | ||
| 64 | #define __FT_TMP3(x) "=c" (x) | ||
| 65 | #define __FT_ARG3(x) "2" ((long) (x)) | ||
| 66 | |||
| 67 | #define ft_event(id, callback) \ | ||
| 68 | __asm__ __volatile__( \ | ||
| 69 | "1: jmp 2f \n\t" \ | ||
| 70 | " call " #callback " \n\t" \ | ||
| 71 | __FT_EVENT_TABLE(id,1b,2f) \ | ||
| 72 | "2: \n\t" \ | ||
| 73 | : : : __FT_CLOBBER_LIST0) | ||
| 74 | |||
| 75 | #define ft_event0(id, callback) \ | ||
| 76 | __asm__ __volatile__( \ | ||
| 77 | "1: jmp 2f \n\t" \ | ||
| 78 | " movq $" #id ", %%rdi \n\t" \ | ||
| 79 | " call " #callback " \n\t" \ | ||
| 80 | __FT_EVENT_TABLE(id,1b,2f) \ | ||
| 81 | "2: \n\t" \ | ||
| 82 | : : : __FT_CLOBBER_LIST0) | ||
| 83 | |||
| 84 | #define ft_event1(id, callback, param) \ | ||
| 85 | do { \ | ||
| 86 | long __ft_tmp1; \ | ||
| 87 | __asm__ __volatile__( \ | ||
| 88 | "1: jmp 2f \n\t" \ | ||
| 89 | " movq $" #id ", %%rdi \n\t" \ | ||
| 90 | " call " #callback " \n\t" \ | ||
| 91 | __FT_EVENT_TABLE(id,1b,2f) \ | ||
| 92 | "2: \n\t" \ | ||
| 93 | : __FT_TMP1(__ft_tmp1) \ | ||
| 94 | : __FT_ARG1(param) \ | ||
| 95 | : __FT_CLOBBER_LIST1); \ | ||
| 96 | } while (0); | ||
| 97 | |||
| 98 | #define ft_event2(id, callback, param, param2) \ | ||
| 99 | do { \ | ||
| 100 | long __ft_tmp1, __ft_tmp2; \ | ||
| 101 | __asm__ __volatile__( \ | ||
| 102 | "1: jmp 2f \n\t" \ | ||
| 103 | " movq $" #id ", %%rdi \n\t" \ | ||
| 104 | " call " #callback " \n\t" \ | ||
| 105 | __FT_EVENT_TABLE(id,1b,2f) \ | ||
| 106 | "2: \n\t" \ | ||
| 107 | : __FT_TMP1(__ft_tmp1), __FT_TMP2(__ft_tmp2) \ | ||
| 108 | : __FT_ARG1(param), __FT_ARG2(param2) \ | ||
| 109 | : __FT_CLOBBER_LIST2); \ | ||
| 110 | } while (0); | ||
| 111 | |||
| 112 | #define ft_event3(id, callback, param, param2, param3) \ | ||
| 113 | do { \ | ||
| 114 | long __ft_tmp1, __ft_tmp2, __ft_tmp3; \ | ||
| 115 | __asm__ __volatile__( \ | ||
| 116 | "1: jmp 2f \n\t" \ | ||
| 117 | " movq $" #id ", %%rdi \n\t" \ | ||
| 118 | " call " #callback " \n\t" \ | ||
| 119 | __FT_EVENT_TABLE(id,1b,2f) \ | ||
| 120 | "2: \n\t" \ | ||
| 121 | : __FT_TMP1(__ft_tmp1), __FT_TMP2(__ft_tmp2), __FT_TMP3(__ft_tmp3) \ | ||
| 122 | : __FT_ARG1(param), __FT_ARG2(param2), __FT_ARG3(param3) \ | ||
| 123 | : __FT_CLOBBER_LIST3); \ | ||
| 124 | } while (0); | ||
diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile index 79076d75bdbf..53bbbb0dda66 100644 --- a/arch/x86/kernel/Makefile +++ b/arch/x86/kernel/Makefile | |||
| @@ -130,6 +130,8 @@ else | |||
| 130 | obj-y += unwind_guess.o | 130 | obj-y += unwind_guess.o |
| 131 | endif | 131 | endif |
| 132 | 132 | ||
| 133 | obj-$(CONFIG_FEATHER_TRACE) += ft_event.o | ||
| 134 | |||
| 133 | ### | 135 | ### |
| 134 | # 64 bit specific files | 136 | # 64 bit specific files |
| 135 | ifeq ($(CONFIG_X86_64),y) | 137 | ifeq ($(CONFIG_X86_64),y) |
diff --git a/arch/x86/kernel/ft_event.c b/arch/x86/kernel/ft_event.c new file mode 100644 index 000000000000..37cc33252713 --- /dev/null +++ b/arch/x86/kernel/ft_event.c | |||
| @@ -0,0 +1,118 @@ | |||
| 1 | #include <linux/types.h> | ||
| 2 | |||
| 3 | #include <litmus/feather_trace.h> | ||
| 4 | |||
| 5 | /* the feather trace management functions assume | ||
| 6 | * exclusive access to the event table | ||
| 7 | */ | ||
| 8 | |||
| 9 | #ifndef CONFIG_DEBUG_RODATA | ||
| 10 | |||
| 11 | #define BYTE_JUMP 0xeb | ||
| 12 | #define BYTE_JUMP_LEN 0x02 | ||
| 13 | |||
| 14 | /* for each event, there is an entry in the event table */ | ||
| 15 | struct trace_event { | ||
| 16 | long id; | ||
| 17 | long count; | ||
| 18 | long start_addr; | ||
| 19 | long end_addr; | ||
| 20 | }; | ||
| 21 | |||
| 22 | extern struct trace_event __start___event_table[]; | ||
| 23 | extern struct trace_event __stop___event_table[]; | ||
| 24 | |||
| 25 | /* Workaround: if no events are defined, then the event_table section does not | ||
| 26 | * exist and the above references cause linker errors. This could probably be | ||
| 27 | * fixed by adjusting the linker script, but it is easier to maintain for us if | ||
| 28 | * we simply create a dummy symbol in the event table section. | ||
| 29 | */ | ||
| 30 | int __event_table_dummy[0] __attribute__ ((section("__event_table"))); | ||
| 31 | |||
| 32 | int ft_enable_event(unsigned long id) | ||
| 33 | { | ||
| 34 | struct trace_event* te = __start___event_table; | ||
| 35 | int count = 0; | ||
| 36 | char* delta; | ||
| 37 | unsigned char* instr; | ||
| 38 | |||
| 39 | while (te < __stop___event_table) { | ||
| 40 | if (te->id == id && ++te->count == 1) { | ||
| 41 | instr = (unsigned char*) te->start_addr; | ||
| 42 | /* make sure we don't clobber something wrong */ | ||
| 43 | if (*instr == BYTE_JUMP) { | ||
| 44 | delta = (((unsigned char*) te->start_addr) + 1); | ||
| 45 | *delta = 0; | ||
| 46 | } | ||
| 47 | } | ||
| 48 | if (te->id == id) | ||
| 49 | count++; | ||
| 50 | te++; | ||
| 51 | } | ||
| 52 | |||
| 53 | printk(KERN_DEBUG "ft_enable_event: enabled %d events\n", count); | ||
| 54 | return count; | ||
| 55 | } | ||
| 56 | |||
| 57 | int ft_disable_event(unsigned long id) | ||
| 58 | { | ||
| 59 | struct trace_event* te = __start___event_table; | ||
| 60 | int count = 0; | ||
| 61 | char* delta; | ||
| 62 | unsigned char* instr; | ||
| 63 | |||
| 64 | while (te < __stop___event_table) { | ||
| 65 | if (te->id == id && --te->count == 0) { | ||
| 66 | instr = (unsigned char*) te->start_addr; | ||
| 67 | if (*instr == BYTE_JUMP) { | ||
| 68 | delta = (((unsigned char*) te->start_addr) + 1); | ||
| 69 | *delta = te->end_addr - te->start_addr - | ||
| 70 | BYTE_JUMP_LEN; | ||
| 71 | } | ||
| 72 | } | ||
| 73 | if (te->id == id) | ||
| 74 | count++; | ||
| 75 | te++; | ||
| 76 | } | ||
| 77 | |||
| 78 | printk(KERN_DEBUG "ft_disable_event: disabled %d events\n", count); | ||
| 79 | return count; | ||
| 80 | } | ||
| 81 | |||
| 82 | int ft_disable_all_events(void) | ||
| 83 | { | ||
| 84 | struct trace_event* te = __start___event_table; | ||
| 85 | int count = 0; | ||
| 86 | char* delta; | ||
| 87 | unsigned char* instr; | ||
| 88 | |||
| 89 | while (te < __stop___event_table) { | ||
| 90 | if (te->count) { | ||
| 91 | instr = (unsigned char*) te->start_addr; | ||
| 92 | if (*instr == BYTE_JUMP) { | ||
| 93 | delta = (((unsigned char*) te->start_addr) | ||
| 94 | + 1); | ||
| 95 | *delta = te->end_addr - te->start_addr - | ||
| 96 | BYTE_JUMP_LEN; | ||
| 97 | te->count = 0; | ||
| 98 | count++; | ||
| 99 | } | ||
| 100 | } | ||
| 101 | te++; | ||
| 102 | } | ||
| 103 | return count; | ||
| 104 | } | ||
| 105 | |||
| 106 | int ft_is_event_enabled(unsigned long id) | ||
| 107 | { | ||
| 108 | struct trace_event* te = __start___event_table; | ||
| 109 | |||
| 110 | while (te < __stop___event_table) { | ||
| 111 | if (te->id == id) | ||
| 112 | return te->count; | ||
| 113 | te++; | ||
| 114 | } | ||
| 115 | return 0; | ||
| 116 | } | ||
| 117 | |||
| 118 | #endif | ||
