From: Jovi Zhangwei Date: Wed, 9 Oct 2013 14:47:51 +0000 (-0700) Subject: staging: ktap: add to the kernel tree X-Git-Url: http://git.lede-project.org./?a=commitdiff_plain;h=c63a164271f81220ff4966d41218a9101f3d0ec4;p=openwrt%2Fstaging%2Fblogic.git staging: ktap: add to the kernel tree This patch introduces ktap to staging tree. ktap is a new script-based dynamic tracing tool for Linux, it uses a scripting language and lets users trace the Linux kernel dynamically. ktap is designed to give operational insights with interoperability that allow users to tune, troubleshoot and extend kernel and application. It's similar with Linux Systemtap and Solaris Dtrace. ktap have different design principles from Linux mainstream dynamic tracing language in that it's based on bytecode, so it doesn't depend upon GCC, doesn't require compiling kernel module for each script, safe to use in production environment, fulfilling the embedded ecosystem's tracing needs. See ktap tutorial for more information: http://www.ktap.org/doc/tutorial.html The merit of putting this software in staging tree is to make it more possible to get feedback from users and thus polish the code. Signed-off-by: Jovi Zhangwei Signed-off-by: Greg Kroah-Hartman --- diff --git a/MAINTAINERS b/MAINTAINERS index 0ad4692c2820..500c8fa86133 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4748,6 +4748,13 @@ S: Maintained F: Documentation/hwmon/k8temp F: drivers/hwmon/k8temp.c +KTAP +M: zhangwei(Jovi) +W: http://www.ktap.org +L: ktap@freelists.org +S: Maintained +F: drivers/staging/ktap/ + KCONFIG M: Michal Marek L: linux-kbuild@vger.kernel.org diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 3bfdaa8d80a9..3b1501b7d894 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -150,4 +150,6 @@ source "drivers/staging/dgnc/Kconfig" source "drivers/staging/dgap/Kconfig" +source "drivers/staging/ktap/Kconfig" + endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index b0d3303b4680..2270ed077bde 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -67,3 +67,4 @@ obj-$(CONFIG_XILLYBUS) += xillybus/ obj-$(CONFIG_DGNC) += dgnc/ obj-$(CONFIG_DGAP) += dgap/ obj-$(CONFIG_MTD_SPINAND_MT29F) += mt29f_spinand/ +obj-$(CONFIG_KTAP) += ktap/ diff --git a/drivers/staging/ktap/Kconfig b/drivers/staging/ktap/Kconfig new file mode 100644 index 000000000000..21f8d2ed03b9 --- /dev/null +++ b/drivers/staging/ktap/Kconfig @@ -0,0 +1,21 @@ +config KTAP + tristate "a programable dynamic tracing tool for Linux" + depends on PERF_EVENTS && EVENT_TRACING + default n + help + ktap is a new script-based dynamic tracing tool for Linux, + it uses a scripting language and lets users trace the + Linux kernel dynamically. ktap is designed to give + operational insights with interoperability that allow + users to tune, troubleshoot and extend kernel and application. + It's similar with Linux Systemtap and Solaris Dtrace. + + ktap have different design principles from Linux mainstream + dynamic tracing language in that it's based on bytecode, + so it doesn't depend upon GCC, doesn't require compiling + kernel module for each script, safe to use in production + environment, fulfilling the embedded ecosystem's tracing needs. + + See ktap tutorial for more information: + http://www.ktap.org/doc/tutorial.html + diff --git a/drivers/staging/ktap/Makefile b/drivers/staging/ktap/Makefile new file mode 100644 index 000000000000..e2e54baf36d9 --- /dev/null +++ b/drivers/staging/ktap/Makefile @@ -0,0 +1,101 @@ + +# Do not instrument the tracer itself: +ifdef CONFIG_FUNCTION_TRACER +ORIG_CFLAGS := $(KBUILD_CFLAGS) +KBUILD_CFLAGS = $(subst -pg,,$(ORIG_CFLAGS)) +endif + +all: mod ktap + +INTP = interpreter + +LIBDIR = $(INTP)/library + +LIB_OBJS += $(LIBDIR)/baselib.o $(LIBDIR)/kdebug.o $(LIBDIR)/timer.o \ + $(LIBDIR)/ansilib.o + +INTP_OBJS += $(INTP)/ktap.o $(INTP)/loader.o $(INTP)/object.o \ + $(INTP)/tstring.o $(INTP)/table.o $(INTP)/vm.o \ + $(INTP)/opcode.o $(INTP)/strfmt.o $(INTP)/transport.o \ + $(LIB_OBJS) + +obj-m += ktapvm.o +ktapvm-y := $(INTP_OBJS) + +KVERSION ?= $(shell uname -r) +KERNEL_SRC ?= /lib/modules/$(KVERSION)/build +mod: + $(MAKE) -C $(KERNEL_SRC) M=$(PWD) modules + +modules_install: + $(MAKE) -C $(KERNEL_SRC) M=$(PWD) modules_install + +KTAPC_CFLAGS = -Wall -O2 + +UDIR = userspace + +$(UDIR)/lex.o: $(UDIR)/lex.c + $(QUIET_CC)$(CC) $(DEBUGINFO_FLAG) $(KTAPC_CFLAGS) -o $@ -c $< +$(UDIR)/parser.o: $(UDIR)/parser.c + $(QUIET_CC)$(CC) $(DEBUGINFO_FLAG) $(KTAPC_CFLAGS) -o $@ -c $< +$(UDIR)/code.o: $(UDIR)/code.c + $(QUIET_CC)$(CC) $(DEBUGINFO_FLAG) $(KTAPC_CFLAGS) -o $@ -c $< +$(UDIR)/dump.o: $(UDIR)/dump.c + $(QUIET_CC)$(CC) $(DEBUGINFO_FLAG) $(KTAPC_CFLAGS) -o $@ -c $< +$(UDIR)/main.o: $(UDIR)/main.c + $(QUIET_CC)$(CC) $(DEBUGINFO_FLAG) $(KTAPC_CFLAGS) -o $@ -c $< +$(UDIR)/util.o: $(UDIR)/util.c + $(QUIET_CC)$(CC) $(DEBUGINFO_FLAG) $(KTAPC_CFLAGS) -o $@ -c $< +$(UDIR)/ktapio.o: $(UDIR)/ktapio.c + $(QUIET_CC)$(CC) $(DEBUGINFO_FLAG) $(KTAPC_CFLAGS) -o $@ -c $< +$(UDIR)/eventdef.o: $(UDIR)/eventdef.c + $(QUIET_CC)$(CC) $(DEBUGINFO_FLAG) $(KTAPC_CFLAGS) -o $@ -c $< +$(UDIR)/opcode.o: $(INTP)/opcode.c + $(QUIET_CC)$(CC) $(DEBUGINFO_FLAG) $(KTAPC_CFLAGS) -o $@ -c $< +$(UDIR)/table.o: $(INTP)/table.c + $(QUIET_CC)$(CC) $(DEBUGINFO_FLAG) $(KTAPC_CFLAGS) -o $@ -c $< +$(UDIR)/tstring.o: $(INTP)/tstring.c + $(QUIET_CC)$(CC) $(DEBUGINFO_FLAG) $(KTAPC_CFLAGS) -o $@ -c $< +$(UDIR)/object.o: $(INTP)/object.c + $(QUIET_CC)$(CC) $(DEBUGINFO_FLAG) $(KTAPC_CFLAGS) -o $@ -c $< + +KTAPOBJS = +KTAPOBJS += $(UDIR)/lex.o +KTAPOBJS += $(UDIR)/parser.o +KTAPOBJS += $(UDIR)/code.o +KTAPOBJS += $(UDIR)/dump.o +KTAPOBJS += $(UDIR)/main.o +KTAPOBJS += $(UDIR)/util.o +KTAPOBJS += $(UDIR)/ktapio.o +KTAPOBJS += $(UDIR)/eventdef.o +KTAPOBJS += $(UDIR)/opcode.o +KTAPOBJS += $(UDIR)/table.o +KTAPOBJS += $(UDIR)/tstring.o +KTAPOBJS += $(UDIR)/object.o + +ktap: $(KTAPOBJS) + $(QUIET_LINK)$(CC) $(KTAPC_CFLAGS) -o $@ $(KTAPOBJS) -lpthread + +KMISC := /lib/modules/$(KVERSION)/ktapvm/ + +install: mod ktap + install -d $(KMISC) + install -m 644 -c *.ko /lib/modules/$(KVERSION)/ktapvm/ + /sbin/depmod -a + +load: + insmod ktapvm.ko + +unload: + rmmod ktapvm + +test: FORCE + cd test; sh ./run_test.sh; cd - + +clean: + $(MAKE) -C $(KERNEL_SRC) M=$(PWD) clean + $(RM) ktap + +PHONY += FORCE +FORCE: + diff --git a/drivers/staging/ktap/README.md b/drivers/staging/ktap/README.md new file mode 100644 index 000000000000..c8ddd5fa66c6 --- /dev/null +++ b/drivers/staging/ktap/README.md @@ -0,0 +1,144 @@ +# ktap + +A New Scripting Dynamic Tracing Tool For Linux +[www.ktap.org][homepage] + +ktap is a new scripting dynamic tracing tool for Linux, +it uses a scripting language and lets users trace the Linux kernel dynamically. +ktap is designed to give operational insights with interoperability +that allows users to tune, troubleshoot and extend kernel and application. +It's similar with Linux Systemtap and Solaris Dtrace. + +ktap have different design principles from Linux mainstream dynamic tracing +language in that it's based on bytecode, so it doesn't depend upon GCC, +doesn't require compiling kernel module for each script, safe to use in +production environment, fulfilling the embedded ecosystem's tracing needs. + +More information can be found at [ktap homepage][homepage]. + +[homepage]: http://www.ktap.org + +## Highlights + + * simple but powerful scripting language + * register based interpreter (heavily optimized) in Linux kernel + * small and lightweight (6KLOC of interpreter) + * not depend on gcc for each script running + * easy to use in embedded environment without debugging info + * support for tracepoint, kprobe, uprobe, function trace, timer, and more + * supported in x86, arm, ppc, mips + * safety in sandbox + +## Building & Running + +1. Clone ktap from github + + $ git clone http://github.com/ktap/ktap.git + +2. Compiling ktap + + $ cd ktap + $ make #generate ktapvm kernel module and ktap binary + +3. Load ktapvm kernel module(make sure debugfs mounted) + + $ make load #need to be root or have sudo access + +4. Running ktap + + $ ./ktap scripts/helloworld.kp + + +## Examples + +1. simplest one-liner command to enable all tracepoints + + ktap -e "trace *:* { print(argevent) }" + +2. syscall tracing on target process + + ktap -e "trace syscalls:* { print(argevent) }" -- ls + +3. function tracing + + ktap -e "trace ftrace:function { print(argevent) }" + + ktap -e "trace ftrace:function /ip==mutex*/ { print(argevent) }" + +4. simple syscall tracing + + trace syscalls:* { + print(cpu(), pid(), execname(), argevent) + } + +5. syscall tracing in histogram style + + s = {} + + trace syscalls:sys_enter_* { + s[argname] += 1 + } + + trace_end { + histogram(s) + } + +6. kprobe tracing + + trace probe:do_sys_open dfd=%di fname=%dx flags=%cx mode=+4($stack) { + print("entry:", execname(), argevent) + } + + trace probe:do_sys_open%return fd=$retval { + print("exit:", execname(), argevent) + } + +7. uprobe tracing + + trace probe:/lib/libc.so.6:0x000773c0 { + print("entry:", execname(), argevent) + } + + trace probe:/lib/libc.so.6:0x000773c0%return { + print("exit:", execname(), argevent) + } + +8. timer + + tick-1ms { + printf("time fired on one cpu\n"); + } + + profile-2s { + printf("time fired on every cpu\n"); + } + +More sample scripts can be found at scripts/ directory. + +## Mailing list + +ktap@freelists.org +You can subscribe to ktap mailing list at link (subscribe before posting): +http://www.freelists.org/list/ktap + + +## Copyright and License + +ktap is licensed under GPL v2 + +Copyright (C) 2012-2013, Jovi Zhangwei . +All rights reserved. + + +## Contribution + +ktap is still under active development, so contributions are welcome. +You are encouraged to report bugs, provide feedback, send feature request, +or hack on it. + + +## See More + +More info can be found at [documentation][tutorial] +[tutorial]: http://www.ktap.org/doc/tutorial.html + diff --git a/drivers/staging/ktap/doc/tutorial.md b/drivers/staging/ktap/doc/tutorial.md new file mode 100644 index 000000000000..3c32ce77a731 --- /dev/null +++ b/drivers/staging/ktap/doc/tutorial.md @@ -0,0 +1,552 @@ +% The ktap Tutorial + +# Introduction + +ktap is a new scripting dynamic tracing tool for linux + +ktap is a new scripting dynamic tracing tool for Linux, +it uses a scripting language and lets users trace the Linux kernel dynamically. +ktap is designed to give operational insights with interoperability +that allows users to tune, troubleshoot and extend kernel and application. +It's similar with Linux Systemtap and Solaris Dtrace. + +ktap have different design principles from Linux mainstream dynamic tracing +language in that it's based on bytecode, so it doesn't depend upon GCC, +doesn't require compiling kernel module for each script, safe to use in +production environment, fulfilling the embedded ecosystem's tracing needs. + +Highlights features: + +* simple but powerful scripting language +* register based interpreter (heavily optimized) in Linux kernel +* small and lightweight (6KLOC of interpreter) +* not depend on gcc for each script running +* easy to use in embedded environment without debugging info +* support for tracepoint, kprobe, uprobe, function trace, timer, and more +* supported in x86, arm, ppc, mips +* safety in sandbox + + +# Getting started + +Requirements + +* Linux 3.1 or later(Need some kernel patches for kernel earlier than 3.1) +* CONFIG_EVENT_TRACING enabled +* CONFIG_PERF_EVENTS enabled +* CONFIG_DEBUG_FS enabled + (make sure debugfs mounted before insmod ktapvm + mount debugfs: mount -t debugfs none /sys/kernel/debug/) + +Note that those configuration is always enabled in Linux distribution, +like REHL, Fedora, Ubuntu, etc. + +1. Clone ktap from github + + $ git clone http://github.com/ktap/ktap.git + +2. Compiling ktap + + $ cd ktap + $ make #generate ktapvm kernel module and ktap binary + +3. Load ktapvm kernel module(make sure debugfs mounted) + + $ make load #need to be root or have sudo access + +4. Running ktap + + $ ./ktap scripts/helloworld.kp + + +# Language basics + +## Syntax basics + +ktap's syntax is design on the mind of C language syntax friendly, +to make it easy scripting by kernel developer. + +1. Variable declaration +The biggest syntax differences with C is that ktap is a dynamic typed +language, so you won't need add any variable type declaration, just +use the variable. + +2. function +All functions in ktap should use keyword "function" declaration + +3. comments +The comments of ktap is starting from '#', long comments doesn't support now. + +4. others +Don't need place any ';' at the ending of statement in ktap. +ktap use free syntax style, so you can choose to use the ';' or not. + +ktap use nil as NULL, the result of any number operate on nil is nil. + +ktap don't have array structure, also don't have any pointer operation. + +## Control structures + +ktap if/else is same as C language. + +There have two method of for-loop in ktap: + + for (i = init, limit, step) { body } + +this is same as below in C: + + for (i = init; i < limit; i += step) { body } + +The next for-loop method is: + + for (k, v in pairs(t)) { body } # looping all elements of table + +Note that ktap don't have "continue" keyword, but C does. + +## Date structures + +Associative array is heavily used in ktap, it's also called by table. + +table declaration: + + t = {} + +how to use table: + + t[1] = 1 + t[1] = "xxx" + t["key"] = 10 + t["key"] = "value" + + for (k, v in pairs(t)) { body } # looping all elements of table + + +# Built in functions and librarys + +## Built in functions + +**print (...)** +Receives any number of arguments, and prints their values, +print is not intended for formatted output, but only as a +quick way to show a value, typically for debugging. +For formatted output, use printf. + +**printf (fmt, ...)** +Similar with C printf, use for format string output. + +**pairs (t)** +Returns three values: the next function, the table t, and nil, +so that the construction +for (k,v in pairs(t)) { body } +will iterate over all key-value pairs of table t. + +**len (t) /len (s)** +If the argument is string, return length of string, +if the argument is table, return counts of table pairs. + +**in_interrupt ()** +checking is context is interrupt context + +**exit ()** +quit ktap executing, similar with exit syscall + +**pid ()** +return current process pid + +**execname ()** +return current process exec name string + +**cpu ()** +return current cpu id + +**arch ()** +return machine architecture, like x86, arm, etc. + +**kernel_v ()** +return Linux kernel version string, like 3.9, etc. + +**user_string (addr)** +Receive userspace address, read string from userspace, return string. + +**histogram (t)** +Receive table, output table histogram to user. + +**curr_task_info (offset, fetch_bytes)** +fetch value in field offset of task_struct structure, argument fetch_bytes +could be 4 or 8, if fetch_bytes is not given, default is 4. + +user may need to get field offset by gdb, for example: +gdb vmlinux +(gdb)p &(((struct task_struct *)0).prio) + +**print_backtrace ()** +print current task stack info + + +## Librarys + +### Kdebug Library + +**kdebug.probe_by_id (event_ids, eventfun)** + +This function is underly representation of high level tracing primitive. +event_ids is the id of all events, it's read from +/sys/kernel/debug/tracing/events/$SYS/$EVENT/id + +for multi-events tracing, the event_ids is concatenation of all id, for example: + "2 3 4", seperated by blank space. + +The second argument in above examples is a function: +function eventfun () { action } + + +**kdebug.probe_end (endfunc)** + +This function is used for invoking a function when tracing end, it will wait +until user press CTRL+C to stop tracing, then ktap will call endfunc function, +user could show tracing results in that function, or do other things. + + +### Timer Library + + + +# Linux tracing basics + +tracepoints, probe, timer +filters +above explaintion +Ring buffer + +# Tracing semantics in ktap + +## Tracing block + +**trace EVENTDEF /FILTER/ { ACTION }** + +This is the basic tracing block for ktap, you need to use a specific EVENTDEF +string, and own event function. + +EVENTDEF is compatible with perf(see perf-list), with glob match, for example: + + syscalls:* trace all syscalls events + syscalls:sys_enter_* trace all syscalls entry events + kmem:* trace all kmem related events + sched:* trace all sched related events + *:* trace all tracepoints in system. + +All events are based on: /sys/kernel/debug/tracing/events/$SYS/$EVENT + +**trace_end { ACTION }** + +This is based on kdebug.probe_end function. + +## Tracing built-in variable + +**argevent** +event object, you can print it by: print(argevent), it will print events +into human readable string, the result is mostly same as each entry of +/sys/kernel/debug/tracing/trace + +**argname** +event name, each event have a name associated with it. + +**arg1..9** +get argument 1..9 of event object. + + +## Timer syntax + +**tick-Ns { ACTION }** +**tick-Nsec { ACTION }** +**tick-Nms { ACTION }** +**tick-Nmsec { ACTION }** +**tick-Nus { ACTION }** +**tick-Nusec { ACTION }** + +**profile-Ns { ACTION }** +**profile-Nsec { ACTION }** +**profile-Nms { ACTION }** +**profile-Nmsec { ACTION }** +**profile-Nus { ACTION }** +**profile-Nusec { ACTION }** + +architecture overview picture reference(pnp format) +one-liners +simple event tracing + +# Advanced tracing pattern + +Aggregation/Histogram +thread local +flame graph + +# Overhead/Performance + +ktap have more fast boot time thant Systemtap(try the helloword script) +ktap have little memory usage than Systemtap +and some scripts show that ktap have a little overhead then Systemtap +(we choosed two scripts to compare, function profile, stack profile. +this is not means all scripts in Systemtap have big overhead than ktap) + + +# FAQ + +**Q: Why use bytecode design?** +A: Using bytecode would be a clean and lightweight solution, + you don't need gcc toolchain to compile every scripts, all you + need is a ktapvm kernel modules and userspace tool called ktap. + Since its language virtual machine design, it have great portability, + suppose you are working at a multi-arch cluster, if you want to run + a tracing script on each board, you won't need cross-compile tracing + script onto all board, what you really need to do is use ktap tool + to run script just in time. + + Bytecode based design also will make executing more safer, than native code + generation. + + Reality already showing that SystemTap is not widely used in embedded Linux, + caused by problem of SystemTap's architecture design choice, it's a natural + design for Redhat and IBM, because Redhat/IBM is focusing on server area, + not embedded area. + +**Q: What's the differences with SystemTap and Dtrace?** +A: For SystemTap, the answer is already mentioned at above question, + SystemTap use translator design, for trade-off on performance with usability, + based on GCC, that's what ktap want to solve. + + For Dtrace, one common design with Dtrace is also use bytecode, so basically + Dtrace and ktap is on the same road. There have some projects aim to porting + Dtrace from Solaris to Linux, but the process is still on the road, Dtrace + is rooted in Solaris, and there have many huge differences between Solaris + tracing infrastructure with Linux's. + + Dtrace is based on D language, a language subset of C, it's a restricted + language, like without for-looping, for safty use in production system. + It seems that Dtrace for Linux only support x86 architecture, not work on + powerpc and arm/mips, obviously it's not suit for embedded Linux currently. + + Dtrace use ctf as input for debuginfo handing, compare with vmlinux for + SystemTap. + + On the license part, Dtrace is released as CDDL, which is incompatible with + GPL(this is why it's impossible to upstream Dtrace into mainline). + +**Q: Why use dynamically typed language? but not statically typed language?** +A: It's hard to say which one is more better than other, dynamically typed + language bring efficiency and fast prototype production, but loosing type + check at compiling phase, and easy to make mistake in runtime, also it's + need many runtime checking, In contrast, statically typed language win on + programing safety, and performance. Statically language would suit for + interoperate with kernel, as kernel is wrote mainly in C, Need to note that + SystemTap and Dtrace both is statically language. + + ktap choose dynamically typed language as initial implementation. + +**Q: Why we need ktap for event tracing? There already have a built-in ftrace** +A: This also is a common question for all dynamic tracing tool, not only ktap. + ktap provide more flexibility than built-in tracing infrastructure. Suppose + you need print a global variable when tracepoint hit, or you want print + backtrace, even more, you want to store some info into associative array, and + display it in histogram style when tracing end, in these case, some of them + ftrace can take it, some of them ftrace can not. + Overall, ktap provide you with great flexibility to scripting your own trace + need. + +**Q: How about the performance? Is ktap slow?** +A: ktap is not slow, the bytecode is very high-level, based on lua, the language + virtual machine is register-based(compare with stack-based), with little + instruction, the table data structure is heavily optimized in ktapvm. + ktap use per-cpu allocation in many place, without global locking scheme, + it's very fast when executing tracepoint callback. + Performance benchmark showing that the overhead of ktap running is nearly + 10%(store event name into associative array), compare with full speed + running without any tracepoint enabled. + + ktap will optimize overhead all the time, hopefully the overhead will + decrease to little than 5%, even more. + +**Q: Why not porting a high level language implementation into kernel directly? + Like python/JVM?** +A: I take serious on the size of vm and memory footprint. Python vm is large, + it's not suit to embed into kernel, and python have some functionality + which we don't need. + + The bytecode of other high level language is also big, ktap only have 32 + bytecodes, python/java/erlang have nearly two hundred bytecodes. + There also have some problems when porting those language into kernel, + userspace programming have many differences with kernel programming, + like float numbers, handle sleeping code carefully in kernel, deadloop is + not allowed in kernel, multi-thread management, etc.., so it's impossible + to porting language implementation into kernel with little adaption work. + +**Q: What's the status of ktap now?** +A: Basically it works on x86-32, x86-64, powerpc, arm, it also could work for + other hardware architecture, but not proven yet(I don't have enough hardware + to test) + If you found some bug, fix it on you own programming skill, or report to me. + +**Q: How to hack ktap? I want to write some extensions onto ktap.** +A: welcome hacking. + You can write your own library to fulfill your specific need, + you can write any script as you want. + +**Q: What's the plan of ktap? any roadmap?** +A: the current plan is deliver stable ktapvm kernel modules, more ktap script, + and bugfix. + + +# References + +* [Linux Performance Analysis and Tools][LPAT] +* [Dtrace Blog][dtraceblog] +* [Dtrace User Guide][dug] +* [LWN: ktap -- yet another kernel tracer][lwn] +* [ktap introduction in LinuxCon Japan 2013][lcj] + +[LPAT]: http://www.brendangregg.com/Slides/SCaLE_Linux_Performance2013.pdf +[dtraceblog]: http://dtrace.org/blogs/ +[dug]: http://docs.huihoo.com/opensolaris/dtrace-user-guide/html/index.html +[lwn]: http://lwn.net/Articles/551314/ +[lcj]: http://events.linuxfoundation.org/sites/events/files/lcjpcojp13_zhangwei.pdf + + +# History + +* ktap was invented at 2002 +* First RFC sent to LKML at 2012.12.31 +* The code was released in github at 2013.01.18 +* ktap released v0.1 at 2013.05.21 +* ktap released v0.2 at 2013.07.31 + +For more release info, please look at RELEASES.txt in project root directory. + +# Sample scripts + +1. simplest one-liner command to enable all tracepoints + + ktap -e "trace *:* { print(argevent) }" + +2. syscall tracing on target process + + ktap -e "trace syscalls:* { print(argevent) }" -- ls + +3. function tracing + + ktap -e "trace ftrace:function { print(argevent) }" + + ktap -e "trace ftrace:function /ip==mutex*/ { print(argevent) }" + +4. simple syscall tracing + + trace syscalls:* { + print(cpu(), pid(), execname(), argevent) + } + +5. syscall tracing in histogram style + + s = {} + + trace syscalls:sys_enter_* { + s[argname] += 1 + } + + trace_end { + histogram(s) + } + +6. kprobe tracing + + trace probe:do_sys_open dfd=%di fname=%dx flags=%cx mode=+4($stack) { + print("entry:", execname(), argevent) + } + + trace probe:do_sys_open%return fd=$retval { + print("exit:", execname(), argevent) + } + +7. uprobe tracing + + trace probe:/lib/libc.so.6:0x000773c0 { + print("entry:", execname(), argevent) + } + + trace probe:/lib/libc.so.6:0x000773c0%return { + print("exit:", execname(), argevent) + } + +8. timer + + tick-1ms { + printf("time fired on one cpu\n"); + } + + profile-2s { + printf("time fired on every cpu\n"); + } + +More sample scripts can be found at scripts/ directory. + + +# Appendix + +Here is the complete syntax of ktap in extended BNF. +(based on lua syntax: http://www.lua.org/manual/5.1/manual.html#5.1) + + chunk ::= {stat [';']} [laststat [';'] + + block ::= chunk + + stat ::= varlist '=' explist | + functioncall | + { block } | + while exp { block } | + repeat block until exp | + if exp { block {elseif exp { block }} [else block] } | + for Name '=' exp ',' exp [',' exp] { block } | + for namelist in explist { block } | + function funcname funcbody | + local function Name funcbody | + local namelist ['=' explist] + + laststat ::= return [explist] | break + + funcname ::= Name {'.' Name} [':' Name] + + varlist ::= var {',' var} + + var ::= Name | prefixexp '[' exp ']'| prefixexp '.' Name + + namelist ::= Name {',' Name} + + explist ::= {exp ',' exp + + exp ::= nil | false | true | Number | String | '...' | function | + prefixexp | tableconstructor | exp binop exp | unop exp + + prefixexp ::= var | functioncall | '(' exp ')' + + functioncall ::= prefixexp args | prefixexp ':' Name args + + args ::= '(' [explist] ')' | tableconstructor | String + + function ::= function funcbody + + funcbody ::= '(' [parlist] ')' { block } + + parlist ::= namelist [',' '...'] | '...' + + tableconstructor ::= '{' [fieldlist] '}' + + fieldlist ::= field {fieldsep field} [fieldsep] + + field ::= '[' exp ']' '=' exp | Name '=' exp | exp + + fieldsep ::= ',' | ';' + + binop ::= '+' | '-' | '*' | '/' | '^' | '%' | '..' | + '<' | '<=' | '>' | '>=' | '==' | '!=' | + and | or + + unop ::= '-' + diff --git a/drivers/staging/ktap/include/ktap.h b/drivers/staging/ktap/include/ktap.h new file mode 100644 index 000000000000..076dd4aa6477 --- /dev/null +++ b/drivers/staging/ktap/include/ktap.h @@ -0,0 +1,169 @@ +#ifndef __KTAP_H__ +#define __KTAP_H__ + +#include "ktap_types.h" +#include "ktap_opcodes.h" + +#include +#include +#include +#include + +typedef struct ktap_Reg { + const char *name; + ktap_cfunction func; +} ktap_Reg; + +struct ktap_probe_event { + struct list_head list; + struct perf_event *perf; + ktap_state *ks; + ktap_closure *cl; +}; + +/* this structure allocate on stack */ +struct ktap_event { + struct ktap_probe_event *pevent; + struct ftrace_event_call *call; + struct trace_entry *entry; + int entry_size; + struct pt_regs *regs; +}; + +enum { + KTAP_PERCPU_DATA_STATE, + KTAP_PERCPU_DATA_STACK, + KTAP_PERCPU_DATA_BUFFER, + KTAP_PERCPU_DATA_BUFFER2, + KTAP_PERCPU_DATA_BTRACE, + + KTAP_PERCPU_DATA_MAX +}; + +#define KTAP_PERCPU_BUFFER_SIZE (3 * PAGE_SIZE) + +int gettimeofday_us(void); +ktap_state *kp_newstate(struct ktap_parm *parm, struct dentry *dir); +void kp_exit(ktap_state *ks); +void kp_final_exit(ktap_state *ks); +ktap_state *kp_newthread(ktap_state *mainthread); +void kp_exitthread(ktap_state *ks); +ktap_closure *kp_load(ktap_state *ks, unsigned char *buff); +void kp_call(ktap_state *ks, StkId func, int nresults); +void kp_optimize_code(ktap_state *ks, int level, ktap_proto *f); +void kp_register_lib(ktap_state *ks, const char *libname, const ktap_Reg *funcs); +void *kp_percpu_data(int type); + +void kp_init_baselib(ktap_state *ks); +void kp_init_oslib(ktap_state *ks); +void kp_init_kdebuglib(ktap_state *ks); +void kp_init_timerlib(ktap_state *ks); +void kp_init_ansilib(ktap_state *ks); + +int kp_probe_init(ktap_state *ks); +void kp_probe_exit(ktap_state *ks); + +void kp_perf_event_register(ktap_state *ks, struct perf_event_attr *attr, + struct task_struct *task, char *filter, + ktap_closure *cl); + +void kp_event_getarg(ktap_state *ks, ktap_value *ra, int n); +void kp_event_tostring(ktap_state *ks, struct trace_seq *seq); + +int kp_strfmt(ktap_state *ks, struct trace_seq *seq); + +void kp_transport_write(ktap_state *ks, const void *data, size_t length); +void kp_transport_event_write(ktap_state *ks, struct ktap_event *e); +void kp_transport_print_backtrace(ktap_state *ks); +void *kp_transport_reserve(ktap_state *ks, size_t length); +void kp_transport_exit(ktap_state *ks); +int kp_transport_init(ktap_state *ks, struct dentry *dir); + +void kp_exit_timers(ktap_state *ks); + +extern int kp_max_exec_count; + +/* get from kernel/trace/trace.h */ +static __always_inline int trace_get_context_bit(void) +{ + int bit; + + if (in_interrupt()) { + if (in_nmi()) + bit = 0; + else if (in_irq()) + bit = 1; + else + bit = 2; + } else + bit = 3; + + return bit; +} + +/* use a special timer context kp_state instead use this recursion approach? */ +DECLARE_PER_CPU(int, kp_recursion_context[PERF_NR_CONTEXTS]); + +static __always_inline int get_recursion_context(void) +{ + int rctx = trace_get_context_bit(); + + if (__this_cpu_read(kp_recursion_context[rctx])) + return -1; + + __this_cpu_write(kp_recursion_context[rctx], true); + barrier(); + + return rctx; +} + +static inline void put_recursion_context(int rctx) +{ + barrier(); + __this_cpu_write(kp_recursion_context[rctx], false); +} + + +extern unsigned int kp_stub_exit_instr; + +static inline void set_next_as_exit(ktap_state *ks) +{ + ktap_callinfo *ci; + + ci = ks->ci; + if (!ci) + return; + + ci->u.l.savedpc = &kp_stub_exit_instr; + + /* See precall, ci changed to ci->prev after invoke C function */ + if (ci->prev) { + ci = ci->prev; + ci->u.l.savedpc = &kp_stub_exit_instr; + } +} + +#define kp_verbose_printf(ks, ...) \ + if (G(ks)->parm->verbose) \ + kp_printf(ks, "[verbose] "__VA_ARGS__); + +/* get argument operation macro */ +#define kp_arg(ks, n) ((ks)->ci->func + (n)) +#define kp_arg_nr(ks) ((int)(ks->top - (ks->ci->func + 1))) + +#define kp_arg_check(ks, narg, type) \ + do { \ + if (unlikely(ttypenv(kp_arg(ks, narg)) != type)) { \ + kp_error(ks, "wrong type of argument %d\n", narg);\ + return -1; \ + } \ + } while (0) + + +#if LINUX_VERSION_CODE > KERNEL_VERSION(3, 5, 0) +#define SPRINT_SYMBOL sprint_symbol_no_offset +#else +#define SPRINT_SYMBOL sprint_symbol +#endif + +#endif /* __KTAP_H__ */ diff --git a/drivers/staging/ktap/include/ktap_opcodes.h b/drivers/staging/ktap/include/ktap_opcodes.h new file mode 100644 index 000000000000..31c558b680f4 --- /dev/null +++ b/drivers/staging/ktap/include/ktap_opcodes.h @@ -0,0 +1,240 @@ +#ifndef __KTAP_BYTECODE_H__ +#define __KTAP_BYTECODE_H__ + + +/* opcode is copied from lua initially */ + +typedef enum { +/*---------------------------------------------------------------------- + * name args description + * ------------------------------------------------------------------------*/ +OP_MOVE,/* A B R(A) := R(B) */ +OP_LOADK,/* A Bx R(A) := Kst(Bx) */ +OP_LOADKX,/* A R(A) := Kst(extra arg) */ +OP_LOADBOOL,/* A B C R(A) := (Bool)B; if (C) pc++ */ +OP_LOADNIL,/* A B R(A), R(A+1), ..., R(A+B) := nil */ +OP_GETUPVAL,/* A B R(A) := UpValue[B] */ + +OP_GETTABUP,/* A B C R(A) := UpValue[B][RK(C)] */ +OP_GETTABLE,/* A B C R(A) := R(B)[RK(C)] */ + +OP_SETTABUP,/* A B C UpValue[A][RK(B)] := RK(C) */ +OP_SETTABUP_INCR,/* A B C UpValue[A][RK(B)] += RK(C) */ +OP_SETUPVAL,/* A B UpValue[B] := R(A) */ +OP_SETTABLE,/* A B C R(A)[RK(B)] := RK(C) */ +OP_SETTABLE_INCR,/* A B C R(A)[RK(B)] += RK(C) */ + +OP_NEWTABLE,/* A B C R(A) := {} (size = B,C) */ + +OP_SELF,/* A B C R(A+1) := R(B); R(A) := R(B)[RK(C)] */ + +OP_ADD,/* A B C R(A) := RK(B) + RK(C) */ +OP_SUB,/* A B C R(A) := RK(B) - RK(C) */ +OP_MUL,/* A B C R(A) := RK(B) * RK(C) */ +OP_DIV,/* A B C R(A) := RK(B) / RK(C) */ +OP_MOD,/* A B C R(A) := RK(B) % RK(C) */ +OP_POW,/* A B C R(A) := RK(B) ^ RK(C) */ +OP_UNM,/* A B R(A) := -R(B) */ +OP_NOT,/* A B R(A) := not R(B) */ +OP_LEN,/* A B R(A) := length of R(B) */ + +OP_CONCAT,/* A B C R(A) := R(B).. ... ..R(C) */ + +OP_JMP,/* A sBx pc+=sBx; if (A) close all upvalues >= R(A) + 1 */ +OP_EQ,/* A B C if ((RK(B) == RK(C)) != A) then pc++ */ +OP_LT,/* A B C if ((RK(B) < RK(C)) != A) then pc++ */ +OP_LE,/* A B C if ((RK(B) <= RK(C)) != A) then pc++ */ + +OP_TEST,/* A C if not (R(A) <=> C) then pc++ */ +OP_TESTSET,/* A B C if (R(B) <=> C) then R(A) := R(B) else pc++ */ + +OP_CALL,/* A B C R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */ +OP_TAILCALL,/* A B C return R(A)(R(A+1), ... ,R(A+B-1)) */ +OP_RETURN,/* A B return R(A), ... ,R(A+B-2) (see note) */ + +OP_FORLOOP,/* A sBx R(A)+=R(A+2); + if R(A) >1) /* `sBx' is signed */ + +#define MAXARG_Ax ((1<>POS_OP) & MASK1(SIZE_OP,0)) +#define SET_OPCODE(i,o) ((i) = (((i)&MASK0(SIZE_OP,POS_OP)) | \ + ((((ktap_instruction)o)<>pos) & MASK1(size,0)) +#define setarg(i,v,pos,size) ((i) = (((i)&MASK0(size,pos)) | \ + ((((ktap_instruction)v)<> 4) & 3) +#define getCMode(m) ((enum OpArgMask)(ktap_opmodes[m] >> 2) & 3) +#define testAMode(m) (ktap_opmodes[m] & (1 << 6)) +#define testTMode(m) (ktap_opmodes[m] & (1 << 7)) + + +/* number of list items to accumulate before a SETLIST instruction */ +#define LFIELDS_PER_FLUSH 50 + +extern const char *const ktap_opnames[NUM_OPCODES + 1]; + +#endif /* __KTAP_BYTECODE_H__ */ diff --git a/drivers/staging/ktap/include/ktap_types.h b/drivers/staging/ktap/include/ktap_types.h new file mode 100644 index 000000000000..cf1cef41b584 --- /dev/null +++ b/drivers/staging/ktap/include/ktap_types.h @@ -0,0 +1,674 @@ +#ifndef __KTAP_TYPES_H__ +#define __KTAP_TYPES_H__ + +/* opcode is copied from lua initially */ + +#ifdef __KERNEL__ +#include +#include +#include +#include +#else +typedef char u8; +#include +#include +#include +#endif + +typedef struct ktap_parm { + char *trunk; /* __user */ + int trunk_len; + int argc; + char **argv; /* __user */ + int verbose; + int trace_pid; + int workload; + int trace_cpu; + int print_timestamp; +} ktap_parm; + +/* + * Ioctls that can be done on a ktap fd: + * todo: use _IO macro in include/uapi/asm-generic/ioctl.h + */ +#define KTAP_CMD_IOC_VERSION ('$' + 0) +#define KTAP_CMD_IOC_RUN ('$' + 1) +#define KTAP_CMD_IOC_EXIT ('$' + 3) + +#define KTAP_ENV "_ENV" + +#define KTAP_VERSION_MAJOR "0" +#define KTAP_VERSION_MINOR "2" + +#define KTAP_VERSION "ktap " KTAP_VERSION_MAJOR "." KTAP_VERSION_MINOR +#define KTAP_AUTHOR "Jovi Zhangwei " +#define KTAP_COPYRIGHT KTAP_VERSION " Copyright (C) 2012-2013, " KTAP_AUTHOR + +#define MYINT(s) (s[0] - '0') +#define VERSION (MYINT(KTAP_VERSION_MAJOR) * 16 + MYINT(KTAP_VERSION_MINOR)) +#define FORMAT 0 /* this is the official format */ + +#define KTAP_SIGNATURE "\033ktap" + +/* data to catch conversion errors */ +#define KTAPC_TAIL "\x19\x93\r\n\x1a\n" + +/* size in bytes of header of binary files */ +#define KTAPC_HEADERSIZE (sizeof(KTAP_SIGNATURE) - sizeof(char) + 2 + \ + 6 + sizeof(KTAPC_TAIL) - sizeof(char)) + +typedef int ktap_instruction; + +typedef union ktap_gcobject ktap_gcobject; + +#define CommonHeader ktap_gcobject *next; u8 tt; + +struct ktap_state; +typedef int (*ktap_cfunction) (struct ktap_state *ks); + +typedef union ktap_string { + int dummy; /* ensures maximum alignment for strings */ + struct { + CommonHeader; + u8 extra; /* reserved words for short strings; "has hash" for longs */ + unsigned int hash; + size_t len; /* number of characters in string */ + } tsv; +} ktap_string; + +#define getstr(ts) (const char *)((ts) + 1) +#define eqshrstr(a,b) ((a) == (b)) + +#define svalue(o) getstr(rawtsvalue(o)) + + +union _ktap_value { + ktap_gcobject *gc; /* collectable objects */ + void *p; /* light userdata */ + int b; /* booleans */ + ktap_cfunction f; /* light C functions */ + long n; /* numbers */ +}; + + +typedef struct ktap_value { + union _ktap_value val; + int type; +} ktap_value; + +typedef ktap_value * StkId; + + + +typedef union ktap_udata { + struct { + CommonHeader; + size_t len; /* number of bytes */ + } uv; +} ktap_udata; + +/* + * Description of an upvalue for function prototypes + */ +typedef struct ktap_upvaldesc { + ktap_string *name; /* upvalue name (for debug information) */ + u8 instack; /* whether it is in stack */ + u8 idx; /* index of upvalue (in stack or in outer function's list) */ +} ktap_upvaldesc; + +/* + * Description of a local variable for function prototypes + * (used for debug information) + */ +typedef struct ktap_locvar { + ktap_string *varname; + int startpc; /* first point where variable is active */ + int endpc; /* first point where variable is dead */ +} ktap_locvar; + + +typedef struct ktap_upval { + CommonHeader; + ktap_value *v; /* points to stack or to its own value */ + union { + ktap_value value; /* the value (when closed) */ + struct { /* double linked list (when open) */ + struct ktap_upval *prev; + struct ktap_upval *next; + } l; + } u; +} ktap_upval; + + +#define KTAP_STACK_MAX_ENTRIES 10 + +typedef struct ktap_btrace { + CommonHeader; + unsigned int nr_entries; + unsigned long entries[KTAP_STACK_MAX_ENTRIES]; +} ktap_btrace; + +#define ktap_closure_header \ + CommonHeader; u8 nupvalues; ktap_gcobject *gclist + +typedef struct ktap_cclosure { + ktap_closure_header; + ktap_cfunction f; + ktap_value upvalue[1]; /* list of upvalues */ +} ktap_cclosure; + + +typedef struct ktap_lclosure { + ktap_closure_header; + struct ktap_proto *p; + struct ktap_upval *upvals[1]; /* list of upvalues */ +} ktap_lclosure; + + +typedef struct ktap_closure { + struct ktap_cclosure c; + struct ktap_lclosure l; +} ktap_closure; + + +typedef struct ktap_proto { + CommonHeader; + ktap_value *k; /* constants used by the function */ + ktap_instruction *code; + struct ktap_proto **p; /* functions defined inside the function */ + int *lineinfo; /* map from opcodes to source lines (debug information) */ + struct ktap_locvar *locvars; /* information about local variables (debug information) */ + struct ktap_upvaldesc *upvalues; /* upvalue information */ + ktap_closure *cache; /* last created closure with this prototype */ + ktap_string *source; /* used for debug information */ + int sizeupvalues; /* size of 'upvalues' */ + int sizek; /* size of `k' */ + int sizecode; + int sizelineinfo; + int sizep; /* size of `p' */ + int sizelocvars; + int linedefined; + int lastlinedefined; + u8 numparams; /* number of fixed parameters */ + u8 is_vararg; + u8 maxstacksize; /* maximum stack used by this function */ +} ktap_proto; + + +/* + * information about a call + */ +typedef struct ktap_callinfo { + StkId func; /* function index in the stack */ + StkId top; /* top for this function */ + struct ktap_callinfo *prev, *next; /* dynamic call link */ + short nresults; /* expected number of results from this function */ + u8 callstatus; + int extra; + union { + struct { /* only for Lua functions */ + StkId base; /* base for this function */ + const unsigned int *savedpc; + } l; + struct { /* only for C functions */ + int ctx; /* context info. in case of yields */ + u8 status; + } c; + } u; +} ktap_callinfo; + + +/* + * ktap_tables + */ +typedef union ktap_tkey { + struct { + union _ktap_value value_; + int tt_; + struct ktap_tnode *next; /* for chaining */ + } nk; + ktap_value tvk; +} ktap_tkey; + + +typedef struct ktap_tnode { + ktap_value i_val; + ktap_tkey i_key; +} ktap_tnode; + + +typedef struct ktap_table { + CommonHeader; +#ifdef __KERNEL__ + arch_spinlock_t lock; +#endif + u8 flags; /* 1<

gch) +/* macros to convert a GCObject into a specific value */ +#define rawgco2ts(o) (&((o)->ts)) +#define gco2ts(o) (&rawgco2ts(o)->tsv) + +#define gco2uv(o) (&((o)->uv)) + +#define obj2gco(v) ((ktap_gcobject *)(v)) + + +#ifdef __KERNEL__ +#define ktap_assert(s) +#else +#define ktap_assert(s) +#if 0 +#define ktap_assert(s) \ + do { \ + if (!s) { \ + printf("assert failed %s, %d\n", __func__, __LINE__);\ + exit(0); \ + } \ + } while(0) +#endif +#endif + +#define check_exp(c,e) (e) + + +typedef int ktap_number; + + +#define ktap_number2int(i,n) ((i)=(int)(n)) + + +/* predefined values in the registry */ +#define KTAP_RIDX_MAINTHREAD 1 +#define KTAP_RIDX_GLOBALS 2 +#define KTAP_RIDX_LAST KTAP_RIDX_GLOBALS + + +#define KTAP_TNONE (-1) + +#define KTAP_TNIL 0 +#define KTAP_TBOOLEAN 1 +#define KTAP_TLIGHTUSERDATA 2 +#define KTAP_TNUMBER 3 +#define KTAP_TSTRING 4 +#define KTAP_TSHRSTR (KTAP_TSTRING | (0 << 4)) /* short strings */ +#define KTAP_TLNGSTR (KTAP_TSTRING | (1 << 4)) /* long strings */ +#define KTAP_TTABLE 5 +#define KTAP_TFUNCTION 6 +#define KTAP_TLCL (KTAP_TFUNCTION | (0 << 4)) /* closure */ +#define KTAP_TLCF (KTAP_TFUNCTION | (1 << 4)) /* light C function */ +#define KTAP_TCCL (KTAP_TFUNCTION | (2 << 4)) /* C closure */ +#define KTAP_TUSERDATA 7 +#define KTAP_TTHREAD 8 + +#define KTAP_NUMTAGS 9 + +#define KTAP_TPROTO 11 +#define KTAP_TUPVAL 12 + +#define KTAP_TEVENT 13 + +#define KTAP_TBTRACE 14 + +#define KTAP_TAGGRTABLE 15 +#define KTAP_TAGGRACCVAL 16 +#define KTAP_TAGGRVAL 17 + +#define ttype(o) ((o->type) & 0x3F) +#define settype(obj, t) ((obj)->type = (t)) + + + +/* raw type tag of a TValue */ +#define rttype(o) ((o)->type) + +/* tag with no variants (bits 0-3) */ +#define novariant(x) ((x) & 0x0F) + +/* type tag of a TValue with no variants (bits 0-3) */ +#define ttypenv(o) (novariant(rttype(o))) + +#define val_(o) ((o)->val) + +#define bvalue(o) (val_(o).b) +#define nvalue(o) (val_(o).n) +#define hvalue(o) (&val_(o).gc->h) +#define ahvalue(o) (&val_(o).gc->ah) +#define aggraccvalue(o) (&val_(o).gc->acc) +#define CLVALUE(o) (&val_(o).gc->cl.l) +#define clcvalue(o) (&val_(o).gc->cl.c) +#define clvalue(o) (&val_(o).gc->cl) +#define rawtsvalue(o) (&val_(o).gc->ts) +#define pvalue(o) (&val_(o).p) +#define fvalue(o) (val_(o).f) +#define rawuvalue(o) (&val_(o).gc->u) +#define uvalue(o) (&rawuvalue(o)->uv) +#define evalue(o) (val_(o).p) +#define btvalue(o) (&val_(o).gc->bt) + +#define gcvalue(o) (val_(o).gc) + +#define isnil(o) (o->type == KTAP_TNIL) +#define isboolean(o) (o->type == KTAP_TBOOLEAN) +#define isfalse(o) (isnil(o) || (isboolean(o) && bvalue(o) == 0)) + +#define ttisshrstring(o) ((o)->type == KTAP_TSHRSTR) +#define ttisstring(o) (((o)->type & 0x0F) == KTAP_TSTRING) +#define ttisnumber(o) ((o)->type == KTAP_TNUMBER) +#define ttisfunc(o) ((o)->type == KTAP_TFUNCTION) +#define ttistable(o) ((o)->type == KTAP_TTABLE) +#define ttisaggrtable(o) ((o)->type == KTAP_TAGGRTABLE) +#define ttisaggrval(o) ((o)->type == KTAP_TAGGRVAL) +#define ttisaggracc(o) ((o)->type == KTAP_TAGGRACCVAL) +#define ttisnil(o) ((o)->type == KTAP_TNIL) +#define ttisboolean(o) ((o)->type == KTAP_TBOOLEAN) +#define ttisequal(o1,o2) ((o1)->type == (o2)->type) +#define ttisevent(o) ((o)->type == KTAP_TEVENT) +#define ttisbtrace(o) ((o)->type == KTAP_TBTRACE) + +#define ttisclone(o) ttisbtrace(o) + + +#define setnilvalue(obj) \ + { ktap_value *io = (obj); io->val.n = 0; settype(io, KTAP_TNIL); } + +#define setbvalue(obj, x) \ + { ktap_value *io = (obj); io->val.b = (x); settype(io, KTAP_TBOOLEAN); } + +#define setnvalue(obj, x) \ + { ktap_value *io = (obj); io->val.n = (x); settype(io, KTAP_TNUMBER); } + +#define setaggrvalue(obj, x) \ + { ktap_value *io = (obj); io->val.n = (x); settype(io, KTAP_TAGGRVAL); } + +#define setaggraccvalue(obj,x) \ + { ktap_value *io=(obj); \ + val_(io).gc = (ktap_gcobject *)(x); settype(io, KTAP_TAGGRACCVAL); } + +#define setsvalue(obj, x) \ + { ktap_value *io = (obj); \ + ktap_string *x_ = (x); \ + io->val.gc = (ktap_gcobject *)x_; settype(io, x_->tsv.tt); } + +#define setcllvalue(obj, x) \ + { ktap_value *io = (obj); \ + io->val.gc = (ktap_gcobject *)x; settype(io, KTAP_TLCL); } + +#define sethvalue(obj,x) \ + { ktap_value *io=(obj); \ + val_(io).gc = (ktap_gcobject *)(x); settype(io, KTAP_TTABLE); } + +#define setahvalue(obj,x) \ + { ktap_value *io=(obj); \ + val_(io).gc = (ktap_gcobject *)(x); settype(io, KTAP_TAGGRTABLE); } + +#define setfvalue(obj,x) \ + { ktap_value *io=(obj); val_(io).f=(x); settype(io, KTAP_TLCF); } + +#define setthvalue(L,obj,x) \ + { ktap_value *io=(obj); \ + val_(io).gc = (ktap_gcobject *)(x); settype(io, KTAP_TTHREAD); } + +#define setevalue(obj, x) \ + { ktap_value *io=(obj); val_(io).p = (x); settype(io, KTAP_TEVENT); } + +#define setbtvalue(obj,x) \ + { ktap_value *io=(obj); \ + val_(io).gc = (ktap_gcobject *)(x); settype(io, KTAP_TBTRACE); } + +#define setobj(obj1,obj2) \ + { const ktap_value *io2=(obj2); ktap_value *io1=(obj1); \ + io1->val = io2->val; io1->type = io2->type; } + +#define rawequalobj(t1, t2) \ + (ttisequal(t1, t2) && kp_equalobjv(NULL, t1, t2)) + +#define equalobj(ks, t1, t2) rawequalobj(t1, t2) + +#define incr_top(ks) {ks->top++;} + +#define NUMADD(a, b) ((a) + (b)) +#define NUMSUB(a, b) ((a) - (b)) +#define NUMMUL(a, b) ((a) * (b)) +#define NUMDIV(a, b) ((a) / (b)) +#define NUMUNM(a) (-(a)) +#define NUMEQ(a, b) ((a) == (b)) +#define NUMLT(a, b) ((a) < (b)) +#define NUMLE(a, b) ((a) <= (b)) +#define NUMISNAN(a) (!NUMEQ((a), (a))) + +/* todo: floor and pow in kernel */ +#define NUMMOD(a, b) ((a) % (b)) +#define NUMPOW(a, b) (pow(a, b)) + + +ktap_string *kp_tstring_newlstr(ktap_state *ks, const char *str, size_t l); +ktap_string *kp_tstring_newlstr_local(ktap_state *ks, const char *str, size_t l); +ktap_string *kp_tstring_new(ktap_state *ks, const char *str); +ktap_string *kp_tstring_new_local(ktap_state *ks, const char *str); +int kp_tstring_eqstr(ktap_string *a, ktap_string *b); +unsigned int kp_string_hash(const char *str, size_t l, unsigned int seed); +int kp_tstring_eqlngstr(ktap_string *a, ktap_string *b); +int kp_tstring_cmp(const ktap_string *ls, const ktap_string *rs); +void kp_tstring_resize(ktap_state *ks, int newsize); +void kp_tstring_freeall(ktap_state *ks); + +ktap_value *kp_table_set(ktap_state *ks, ktap_table *t, const ktap_value *key); +ktap_table *kp_table_new(ktap_state *ks); +const ktap_value *kp_table_getint(ktap_table *t, int key); +void kp_table_setint(ktap_state *ks, ktap_table *t, int key, ktap_value *v); +const ktap_value *kp_table_get(ktap_table *t, const ktap_value *key); +void kp_table_setvalue(ktap_state *ks, ktap_table *t, const ktap_value *key, ktap_value *val); +void kp_table_resize(ktap_state *ks, ktap_table *t, int nasize, int nhsize); +void kp_table_resizearray(ktap_state *ks, ktap_table *t, int nasize); +void kp_table_free(ktap_state *ks, ktap_table *t); +int kp_table_length(ktap_state *ks, ktap_table *t); +void kp_table_dump(ktap_state *ks, ktap_table *t); +void kp_table_clear(ktap_state *ks, ktap_table *t); +void kp_table_histogram(ktap_state *ks, ktap_table *t); +int kp_table_next(ktap_state *ks, ktap_table *t, StkId key); +void kp_table_atomic_inc(ktap_state *ks, ktap_table *t, ktap_value *key, int n); +void kp_aggraccval_dump(ktap_state *ks, ktap_aggraccval *acc); +ktap_aggrtable *kp_aggrtable_new(ktap_state *ks); +ktap_table *kp_aggrtable_synthesis(ktap_state *ks, ktap_aggrtable *ah); +void kp_aggrtable_dump(ktap_state *ks, ktap_aggrtable *ah); +void kp_aggrtable_free(ktap_state *ks, ktap_aggrtable *ah); +void kp_aggrtable_set(ktap_state *ks, ktap_aggrtable *ah, + ktap_value *key, ktap_value *val); +void kp_aggrtable_get(ktap_state *ks, ktap_aggrtable *ah, + ktap_value *key, ktap_value *val); +void kp_aggrtable_histogram(ktap_state *ks, ktap_aggrtable *ah); +void kp_obj_dump(ktap_state *ks, const ktap_value *v); +void kp_showobj(ktap_state *ks, const ktap_value *v); +int kp_objlen(ktap_state *ks, const ktap_value *rb); +void kp_objclone(ktap_state *ks, const ktap_value *o, ktap_value *newo, + ktap_gcobject **list); +ktap_gcobject *kp_newobject(ktap_state *ks, int type, size_t size, ktap_gcobject **list); +int kp_equalobjv(ktap_state *ks, const ktap_value *t1, const ktap_value *t2); +ktap_closure *kp_newlclosure(ktap_state *ks, int n); +ktap_proto *kp_newproto(ktap_state *ks); +ktap_upval *kp_newupval(ktap_state *ks); +void kp_free_gclist(ktap_state *ks, ktap_gcobject *o); +void kp_free_all_gcobject(ktap_state *ks); +void kp_header(u8 *h); + +int kp_str2d(const char *s, size_t len, ktap_number *result); + +#define kp_realloc(ks, v, osize, nsize, t) \ + ((v) = (t *)kp_reallocv(ks, v, osize * sizeof(t), nsize * sizeof(t))) + +#define kp_error(ks, args...) \ + do { \ + kp_printf(ks, "error: "args); \ + G(ks)->error = 1; \ + kp_exit(ks); \ + } while(0) + +#ifdef __KERNEL__ +#define G(ks) (ks->g) + +void *kp_malloc(ktap_state *ks, int size); +void kp_free(ktap_state *ks, void *addr); +void *kp_reallocv(ktap_state *ks, void *addr, int oldsize, int newsize); +void *kp_zalloc(ktap_state *ks, int size); + +void kp_printf(ktap_state *ks, const char *fmt, ...); +extern void __kp_puts(ktap_state *ks, const char *str); +extern void __kp_bputs(ktap_state *ks, const char *str); + +#define kp_puts(ks, str) ({ \ + static const char *trace_printk_fmt \ + __attribute__((section("__trace_printk_fmt"))) = \ + __builtin_constant_p(str) ? str : NULL; \ + \ + if (__builtin_constant_p(str)) \ + __kp_bputs(ks, trace_printk_fmt); \ + else \ + __kp_puts(ks, str); \ +}) + +#else +/* + * this is used for ktapc tstring operation, tstring need G(ks)->strt + * and G(ks)->seed, so ktapc need to init those field + */ +#define G(ks) (&dummy_global_state) +extern ktap_global_state dummy_global_state; + +#define kp_malloc(ks, size) malloc(size) +#define kp_free(ks, block) free(block) +#define kp_reallocv(ks, block, osize, nsize) realloc(block, nsize) +#define kp_printf(ks, args...) printf(args) +#define kp_puts(ks, str) printf("%s", str) +#define kp_exit(ks) exit(EXIT_FAILURE) +#endif + +#define __maybe_unused __attribute__((unused)) + +/* + * KTAP_QL describes how error messages quote program elements. + * CHANGE it if you want a different appearance. + */ +#define KTAP_QL(x) "'" x "'" +#define KTAP_QS KTAP_QL("%s") + +#endif /* __KTAP_TYPES_H__ */ + diff --git a/drivers/staging/ktap/interpreter/ktap.c b/drivers/staging/ktap/interpreter/ktap.c new file mode 100644 index 000000000000..18d8fc8ec63d --- /dev/null +++ b/drivers/staging/ktap/interpreter/ktap.c @@ -0,0 +1,235 @@ +/* + * ktap.c - ktapvm kernel module main entry + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * this file is the first file to be compile, add CONFIG_ checking in here. + * See Requirements in doc/introduction.txt + */ + +#include +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 1, 0) +#error "Currently ktap don't support kernel older than 3.1" +#endif + +#if !CONFIG_EVENT_TRACING +#error "Please enable CONFIG_EVENT_TRACING before compile ktap" +#endif + +#if !CONFIG_PERF_EVENTS +#error "Please enable CONFIG_PERF_EVENTS before compile ktap" +#endif + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../include/ktap.h" + +static int load_trunk(struct ktap_parm *parm, unsigned long **buff) +{ + int ret; + unsigned long *vmstart; + + vmstart = vmalloc(parm->trunk_len); + if (!vmstart) + return -ENOMEM; + + ret = copy_from_user(vmstart, (void __user *)parm->trunk, + parm->trunk_len); + if (ret < 0) { + vfree(vmstart); + return -EFAULT; + } + + *buff = vmstart; + return 0; +} + +int gettimeofday_us(void) +{ + struct timeval tv; + + do_gettimeofday(&tv); + return tv.tv_sec * USEC_PER_SEC + tv.tv_usec; +} + +struct dentry *kp_dir_dentry; +static atomic_t kp_is_running = ATOMIC_INIT(0); + +/* Ktap Main Entry */ +static int ktap_main(struct file *file, ktap_parm *parm) +{ + unsigned long *buff = NULL; + ktap_state *ks; + ktap_closure *cl; + int start_time, delta_time; + int ret; + + if (atomic_inc_return(&kp_is_running) != 1) { + atomic_dec(&kp_is_running); + pr_info("only one ktap thread allow to run\n"); + return -EBUSY; + } + + start_time = gettimeofday_us(); + + ks = kp_newstate(parm, kp_dir_dentry); + if (unlikely(!ks)) { + ret = -ENOEXEC; + goto out; + } + + file->private_data = ks; + + ret = load_trunk(parm, &buff); + if (ret) { + pr_err("cannot load file\n"); + goto out; + } + + cl = kp_load(ks, (unsigned char *)buff); + + vfree(buff); + + if (cl) { + /* optimize bytecode before excuting */ + kp_optimize_code(ks, 0, cl->l.p); + + delta_time = gettimeofday_us() - start_time; + kp_verbose_printf(ks, "booting time: %d (us)\n", delta_time); + kp_call(ks, ks->top - 1, 0); + } + + kp_final_exit(ks); + + out: + atomic_dec(&kp_is_running); + return ret; +} + + +static void print_version(void) +{ +} + +static long ktap_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + ktap_parm parm; + int ret; + + switch (cmd) { + case KTAP_CMD_IOC_VERSION: + print_version(); + return 0; + case KTAP_CMD_IOC_RUN: + ret = copy_from_user(&parm, (void __user *)arg, + sizeof(ktap_parm)); + if (ret < 0) + return -EFAULT; + + return ktap_main(file, &parm); + default: + return -EINVAL; + }; + + return 0; +} + +static const struct file_operations ktap_fops = { + .llseek = no_llseek, + .unlocked_ioctl = ktap_ioctl, +}; + +static long ktapvm_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int new_fd, err; + struct file *new_file; + + new_fd = get_unused_fd(); + if (new_fd < 0) + return new_fd; + + new_file = anon_inode_getfile("[ktap]", &ktap_fops, NULL, O_RDWR); + if (IS_ERR(new_file)) { + err = PTR_ERR(new_file); + put_unused_fd(new_fd); + return err; + } + + file->private_data = NULL; + fd_install(new_fd, new_file); + return new_fd; +} + +static const struct file_operations ktapvm_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = ktapvm_ioctl, +}; + +unsigned int kp_stub_exit_instr; + +static int __init init_ktap(void) +{ + struct dentry *ktapvm_dentry; + + kp_dir_dentry = debugfs_create_dir("ktap", NULL); + if (!kp_dir_dentry) { + pr_err("ktap: debugfs_create_dir failed\n"); + return -1; + } + + ktapvm_dentry = debugfs_create_file("ktapvm", 0444, kp_dir_dentry, NULL, + &ktapvm_fops); + + if (!ktapvm_dentry) { + pr_err("ktapvm: cannot create ktapvm file\n"); + debugfs_remove_recursive(kp_dir_dentry); + return -1; + } + + SET_OPCODE(kp_stub_exit_instr, OP_EXIT); + + return 0; +} + +static void __exit exit_ktap(void) +{ + debugfs_remove_recursive(kp_dir_dentry); +} + +module_init(init_ktap); +module_exit(exit_ktap); + +MODULE_AUTHOR("Jovi Zhangwei "); +MODULE_DESCRIPTION("ktap"); +MODULE_LICENSE("GPL"); + +int kp_max_exec_count = 10000; +module_param_named(max_exec_count, kp_max_exec_count, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(max_exec_count, "non-mainthread max instruction execution count"); + diff --git a/drivers/staging/ktap/interpreter/library/ansilib.c b/drivers/staging/ktap/interpreter/library/ansilib.c new file mode 100644 index 000000000000..d058373e5f30 --- /dev/null +++ b/drivers/staging/ktap/interpreter/library/ansilib.c @@ -0,0 +1,153 @@ +/* + * ansilib.c - ANSI escape sequences library + * + * http://en.wikipedia.org/wiki/ANSI_escape_code + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "../../include/ktap.h" + +/** + * function ansi.clear_screen - Move cursor to top left and clear screen. + * + * Description: Sends ansi code for moving cursor to top left and then the + * ansi code for clearing the screen from the cursor position to the end. + */ + +static int ktap_lib_clear_screen(ktap_state *ks) +{ + kp_printf(ks, "\033[1;1H\033[J"); + return 0; +} + +/** + * function ansi.set_color - Set the ansi Select Graphic Rendition mode. + * @fg: Foreground color to set. + * + * Description: Sends ansi code for Select Graphic Rendition mode for the + * given forground color. Black (30), Blue (34), Green (32), Cyan (36), + * Red (31), Purple (35), Brown (33), Light Gray (37). + */ + +static int ktap_lib_set_color(ktap_state *ks) +{ + int fg; + + kp_arg_check(ks, 1, KTAP_TNUMBER); + + fg = nvalue(kp_arg(ks, 1)); + kp_printf(ks, "\033[%dm", fg); + return 0; +} + +/** + * function ansi.set_color2 - Set the ansi Select Graphic Rendition mode. + * @fg: Foreground color to set. + * @bg: Background color to set. + * + * Description: Sends ansi code for Select Graphic Rendition mode for the + * given forground color, Black (30), Blue (34), Green (32), Cyan (36), + * Red (31), Purple (35), Brown (33), Light Gray (37) and the given + * background color, Black (40), Red (41), Green (42), Yellow (43), + * Blue (44), Magenta (45), Cyan (46), White (47). + */ +static int ktap_lib_set_color2(ktap_state *ks) +{ + int fg, bg; + + kp_arg_check(ks, 1, KTAP_TNUMBER); + kp_arg_check(ks, 2, KTAP_TNUMBER); + + fg = nvalue(kp_arg(ks, 1)); + bg = nvalue(kp_arg(ks, 2)); + kp_printf(ks, "\033[%d;%dm", fg, bg); + return 0; +} + +/** + * function ansi.set_color3 - Set the ansi Select Graphic Rendition mode. + * @fg: Foreground color to set. + * @bg: Background color to set. + * @attr: Color attribute to set. + * + * Description: Sends ansi code for Select Graphic Rendition mode for the + * given forground color, Black (30), Blue (34), Green (32), Cyan (36), + * Red (31), Purple (35), Brown (33), Light Gray (37), the given + * background color, Black (40), Red (41), Green (42), Yellow (43), + * Blue (44), Magenta (45), Cyan (46), White (47) and the color attribute + * All attributes off (0), Intensity Bold (1), Underline Single (4), + * Blink Slow (5), Blink Rapid (6), Image Negative (7). + */ +static int ktap_lib_set_color3(ktap_state *ks) +{ + int fg, bg, attr; + + kp_arg_check(ks, 1, KTAP_TNUMBER); + kp_arg_check(ks, 2, KTAP_TNUMBER); + kp_arg_check(ks, 3, KTAP_TNUMBER); + + fg = nvalue(kp_arg(ks, 1)); + bg = nvalue(kp_arg(ks, 2)); + attr = nvalue(kp_arg(ks, 3)); + + if (attr) + kp_printf(ks, "\033[%d;%d;%dm", fg, bg, attr); + else + kp_printf(ks, "\033[%d;%dm", fg, bg); + + return 0; +} + +/** + * function ansi.reset_color - Resets Select Graphic Rendition mode. + * + * Description: Sends ansi code to reset foreground, background and color + * attribute to default values. + */ +static int ktap_lib_reset_color(ktap_state *ks) +{ + kp_printf(ks, "\033[0;0m"); + return 0; +} + +/** + * function ansi.new_line - Move cursor to new line. + * + * Description: Sends ansi code new line. + */ +static int ktap_lib_new_line (ktap_state *ks) +{ + kp_printf(ks, "\12"); + return 0; +} + +static const ktap_Reg ansi_funcs[] = { + {"clear_screen", ktap_lib_clear_screen}, + {"set_color", ktap_lib_set_color}, + {"set_color2", ktap_lib_set_color2}, + {"set_color3", ktap_lib_set_color3}, + {"reset_color", ktap_lib_reset_color}, + {"new_line", ktap_lib_new_line}, + {NULL} +}; + +void kp_init_ansilib(ktap_state *ks) +{ + kp_register_lib(ks, "ansi", ansi_funcs); +} diff --git a/drivers/staging/ktap/interpreter/library/baselib.c b/drivers/staging/ktap/interpreter/library/baselib.c new file mode 100644 index 000000000000..4bcdc620f9d9 --- /dev/null +++ b/drivers/staging/ktap/interpreter/library/baselib.c @@ -0,0 +1,455 @@ +/* + * baselib.c - ktapvm kernel module base library + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../include/ktap.h" + +static int ktap_lib_next(ktap_state *ks) +{ + ktap_table *t = hvalue(ks->top - 2); + + if (kp_table_next(ks, t, ks->top-1)) { + ks->top += 1; + return 2; + } else { + ks->top -= 1; + setnilvalue(ks->top++); + return 1; + } +} + +static int ktap_lib_pairs(ktap_state *ks) +{ + ktap_value *v = kp_arg(ks, 1); + ktap_table *t; + + if (G(ks)->mainthread != ks) { + kp_error(ks, "only mainthread can call table pairs\n"); + return -1; + } + + if (ttistable(v)) { + t = hvalue(v); + } else if (ttisaggrtable(v)) { + t = kp_aggrtable_synthesis(ks, ahvalue(v)); + } else if (isnil(v)) { + kp_error(ks, "table is nil in pairs\n"); + return 0; + } else { + kp_error(ks, "wrong argument for pairs\n"); + return 0; + } + + setfvalue(ks->top++, ktap_lib_next); + sethvalue(ks->top++, t); + setnilvalue(ks->top++); + return 3; +} + +static int ktap_lib_len(ktap_state *ks) +{ + int len = kp_objlen(ks, kp_arg(ks, 1)); + + if (len < 0) + return -1; + + setnvalue(ks->top, len); + incr_top(ks); + return 1; +} + +static int ktap_lib_print(ktap_state *ks) +{ + int i; + int n = kp_arg_nr(ks); + + for (i = 1; i <= n; i++) { + ktap_value *arg = kp_arg(ks, i); + if (i > 1) + kp_puts(ks, "\t"); + kp_showobj(ks, arg); + } + + kp_puts(ks, "\n"); + + return 0; +} + +/* don't engage with tstring when printf, use buffer directly */ +static int ktap_lib_printf(ktap_state *ks) +{ + struct trace_seq *seq; + + preempt_disable_notrace(); + + seq = kp_percpu_data(KTAP_PERCPU_DATA_BUFFER); + trace_seq_init(seq); + + if (kp_strfmt(ks, seq)) + return 0; + + seq->buffer[seq->len] = '\0'; + kp_transport_write(ks, seq->buffer, seq->len + 1); + + preempt_enable_notrace(); + return 0; +} + +#ifdef CONFIG_STACKTRACE +static int ktap_lib_print_backtrace(ktap_state *ks) +{ + kp_transport_print_backtrace(ks); + return 0; +} +#else +static int ktap_lib_print_backtrace(ktap_state *ks) +{ + kp_error(ks, "Please enable CONFIG_STACKTRACE before use " + "ktap print_backtrace\n"); + return 0; +} +#endif + +static int ktap_lib_backtrace(ktap_state *ks) +{ + struct stack_trace trace; + ktap_btrace *bt; + + bt = kp_percpu_data(KTAP_PERCPU_DATA_BTRACE); + + trace.nr_entries = 0; + trace.skip = 10; + trace.max_entries = KTAP_STACK_MAX_ENTRIES; + trace.entries = &bt->entries[0]; + save_stack_trace(&trace); + + bt->nr_entries = trace.nr_entries; + setbtvalue(ks->top, bt); + incr_top(ks); + return 1; +} + +extern unsigned long long ns2usecs(cycle_t nsec); +static int ktap_lib_print_trace_clock(ktap_state *ks) +{ + unsigned long long t; + unsigned long secs, usec_rem; + u64 timestamp; + + /* use ring buffer's timestamp */ + timestamp = ring_buffer_time_stamp(G(ks)->buffer, smp_processor_id()); + + t = ns2usecs(timestamp); + usec_rem = do_div(t, USEC_PER_SEC); + secs = (unsigned long)t; + + kp_printf(ks, "%5lu.%06lu\n", secs, usec_rem); + + return 0; +} + +static int ktap_lib_exit(ktap_state *ks) +{ + kp_exit(ks); + + /* do not execute bytecode any more in this thread */ + return -1; +} + +static int ktap_lib_pid(ktap_state *ks) +{ + pid_t pid = task_tgid_vnr(current); + + setnvalue(ks->top, (int)pid); + incr_top(ks); + return 1; +} + +static int ktap_lib_tid(ktap_state *ks) +{ + pid_t pid = task_pid_vnr(current); + + setnvalue(ks->top, (int)pid); + incr_top(ks); + return 1; +} + +static int ktap_lib_execname(ktap_state *ks) +{ + ktap_string *ts = kp_tstring_new(ks, current->comm); + setsvalue(ks->top, ts); + incr_top(ks); + return 1; +} + +static int ktap_lib_cpu(ktap_state *ks) +{ + setnvalue(ks->top, smp_processor_id()); + incr_top(ks); + return 1; +} + +static int ktap_lib_num_cpus(ktap_state *ks) +{ + setnvalue(ks->top, num_online_cpus()); + incr_top(ks); + return 1; +} + +static int ktap_lib_in_interrupt(ktap_state *ks) +{ + int ret = in_interrupt(); + + setnvalue(ks->top, ret); + incr_top(ks); + return 1; +} + +static int ktap_lib_arch(ktap_state *ks) +{ + setsvalue(ks->top, kp_tstring_new(ks, utsname()->machine)); + incr_top(ks); + return 1; +} + +static int ktap_lib_kernel_v(ktap_state *ks) +{ + setsvalue(ks->top, kp_tstring_new(ks, utsname()->release)); + incr_top(ks); + return 1; +} + +static int ktap_lib_user_string(ktap_state *ks) +{ + unsigned long addr; + char str[256] = {0}; + int ret; + + kp_arg_check(ks, 1, KTAP_TNUMBER); + + addr = nvalue(kp_arg(ks, 1)); + + pagefault_disable(); + ret = __copy_from_user_inatomic((void *)str, (const void *)addr, 256); + (void) &ret; /* Silence compiler warning. */ + pagefault_enable(); + str[255] = '\0'; + setsvalue(ks->top, kp_tstring_new(ks, str)); + + incr_top(ks); + return 1; +} + +static int ktap_lib_histogram(ktap_state *ks) +{ + ktap_value *v = kp_arg(ks, 1); + + if (G(ks)->mainthread != ks) { + kp_error(ks, "only mainthread can call table historgram\n"); + return -1; + } + + if (ttistable(v)) + kp_table_histogram(ks, hvalue(v)); + else if (ttisaggrtable(v)) + kp_aggrtable_histogram(ks, ahvalue(v)); + + return 0; +} + +static int ktap_lib_aggr_table(ktap_state *ks) +{ + ktap_aggrtable *ah; + + ah = kp_aggrtable_new(ks); + setahvalue(ks->top, ah); + incr_top(ks); + return 1; +} + +static int ktap_lib_aggr_count(ktap_state *ks) +{ + setaggrvalue(ks->top, AGGREGATION_TYPE_COUNT); + incr_top(ks); + return 1; +} + +static int ktap_lib_aggr_max(ktap_state *ks) +{ + kp_arg_check(ks, 1, KTAP_TNUMBER); + + ks->aggr_accval = nvalue(kp_arg(ks, 1)); + setaggrvalue(ks->top, AGGREGATION_TYPE_MAX); + incr_top(ks); + return 1; +} + +static int ktap_lib_aggr_min(ktap_state *ks) +{ + kp_arg_check(ks, 1, KTAP_TNUMBER); + + ks->aggr_accval = nvalue(kp_arg(ks, 1)); + setaggrvalue(ks->top, AGGREGATION_TYPE_MIN); + incr_top(ks); + return 1; +} + +static int ktap_lib_aggr_sum(ktap_state *ks) +{ + kp_arg_check(ks, 1, KTAP_TNUMBER); + + ks->aggr_accval = nvalue(kp_arg(ks, 1)); + setaggrvalue(ks->top, AGGREGATION_TYPE_SUM); + incr_top(ks); + return 1; +} + +static int ktap_lib_aggr_avg(ktap_state *ks) +{ + kp_arg_check(ks, 1, KTAP_TNUMBER); + + ks->aggr_accval = nvalue(kp_arg(ks, 1)); + setaggrvalue(ks->top, AGGREGATION_TYPE_AVG); + incr_top(ks); + return 1; +} + +static int ktap_lib_delete(ktap_state *ks) +{ + kp_arg_check(ks, 1, KTAP_TTABLE); + + kp_table_clear(ks, hvalue(kp_arg(ks, 1))); + return 0; +} + +static int ktap_lib_gettimeofday_us(ktap_state *ks) +{ + setnvalue(ks->top, gettimeofday_us()); + incr_top(ks); + + return 1; +} + +/* + * use gdb to get field offset of struct task_struct, for example: + * + * gdb vmlinux + * (gdb)p &(((struct task_struct *)0).prio) + */ +static int ktap_lib_curr_task_info(ktap_state *ks) +{ + int offset; + int fetch_bytes; + + kp_arg_check(ks, 1, KTAP_TNUMBER); + + offset = nvalue(kp_arg(ks, 1)); + + if (kp_arg_nr(ks) == 1) + fetch_bytes = 4; /* default fetch 4 bytes*/ + else { + kp_arg_check(ks, 2, KTAP_TNUMBER); + fetch_bytes = nvalue(kp_arg(ks, 2)); + } + + if (offset >= sizeof(struct task_struct)) { + setnilvalue(ks->top++); + kp_error(ks, "access out of bound value of task_struct\n"); + return 1; + } + +#define RET_VALUE ((unsigned long)current + offset) + + switch (fetch_bytes) { + case 4: + setnvalue(ks->top, *(unsigned int *)RET_VALUE); + break; + case 8: + setnvalue(ks->top, *(unsigned long *)RET_VALUE); + break; + default: + kp_error(ks, "unsupported fetch bytes in curr_task_info\n"); + setnilvalue(ks->top); + break; + } + +#undef RET_VALUE + + incr_top(ks); + return 1; +} + +/* + * This built-in function mainly purpose scripts/schedule/schedtimes.kp + */ +static int ktap_lib_in_iowait(ktap_state *ks) +{ + setnvalue(ks->top, current->in_iowait); + incr_top(ks); + + return 1; +} + +static const ktap_Reg base_funcs[] = { + {"pairs", ktap_lib_pairs}, + {"len", ktap_lib_len}, + {"print", ktap_lib_print}, + {"printf", ktap_lib_printf}, + {"print_backtrace", ktap_lib_print_backtrace}, + {"backtrace", ktap_lib_backtrace}, + {"print_trace_clock", ktap_lib_print_trace_clock}, + {"in_interrupt", ktap_lib_in_interrupt}, + {"exit", ktap_lib_exit}, + {"pid", ktap_lib_pid}, + {"tid", ktap_lib_tid}, + {"execname", ktap_lib_execname}, + {"cpu", ktap_lib_cpu}, + {"num_cpus", ktap_lib_num_cpus}, + {"arch", ktap_lib_arch}, + {"kernel_v", ktap_lib_kernel_v}, + {"user_string", ktap_lib_user_string}, + {"histogram", ktap_lib_histogram}, + {"aggr_table", ktap_lib_aggr_table}, + {"count", ktap_lib_aggr_count}, + {"max", ktap_lib_aggr_max}, + {"min", ktap_lib_aggr_min}, + {"sum", ktap_lib_aggr_sum}, + {"avg", ktap_lib_aggr_avg}, + + {"delete", ktap_lib_delete}, + {"gettimeofday_us", ktap_lib_gettimeofday_us}, + {"curr_taskinfo", ktap_lib_curr_task_info}, + {"in_iowait", ktap_lib_in_iowait}, + {NULL} +}; + +void kp_init_baselib(ktap_state *ks) +{ + kp_register_lib(ks, NULL, base_funcs); +} diff --git a/drivers/staging/ktap/interpreter/library/kdebug.c b/drivers/staging/ktap/interpreter/library/kdebug.c new file mode 100644 index 000000000000..8bbbc491b6c7 --- /dev/null +++ b/drivers/staging/ktap/interpreter/library/kdebug.c @@ -0,0 +1,450 @@ +/* + * kdebug.c - ktap probing core implementation + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include //syscall_set_return_value defined here +#include "../../include/ktap.h" + +static void ktap_call_probe_closure(ktap_state *mainthread, ktap_closure *cl, + struct ktap_event *e) +{ + ktap_state *ks; + ktap_value *func; + + ks = kp_newthread(mainthread); + setcllvalue(ks->top, cl); + func = ks->top; + incr_top(ks); + + ks->current_event = e; + + kp_call(ks, func, 0); + + ks->current_event = NULL; + kp_exitthread(ks); +} + +void kp_event_tostring(ktap_state *ks, struct trace_seq *seq) +{ + struct ktap_event *e = ks->current_event; + struct trace_iterator *iter; + struct trace_event *ev; + enum print_line_t ret = TRACE_TYPE_NO_CONSUME; + + /* Simulate the iterator */ + + /* + * use temp percpu buffer as trace_iterator + * we cannot use same temp buffer as printf. + */ + iter = kp_percpu_data(KTAP_PERCPU_DATA_BUFFER2); + + trace_seq_init(&iter->seq); + iter->ent = e->entry; + + ev = &(e->call->event); + if (ev) + ret = ev->funcs->trace(iter, 0, ev); + + if (ret != TRACE_TYPE_NO_CONSUME) { + struct trace_seq *s = &iter->seq; + int len = s->len >= PAGE_SIZE ? PAGE_SIZE - 1 : s->len; + + s->buffer[len] = '\0'; + trace_seq_puts(seq, s->buffer); + } +} + +#if 0 +/* check pt_regs defintion in linux/arch/x86/include/asm/ptrace.h */ +/* support other architecture pt_regs showing */ +static void event_regstr(ktap_state *ks, struct ktap_event *e, StkId ra) +{ + struct pt_regs *regs = e->regs; + char str[256] = {0}; + +#if defined(CONFIG_X86_32) + snprintf(str, sizeof(str), + "{ax: 0x%lx, orig_ax: 0x%lx, bx: 0x%lx, cx: 0x%lx, dx: 0x%lx, " + "si: 0x%lx, di: 0x%lx, bp: 0x%lx, ds: 0x%lx, es: 0x%lx, fs: 0x%lx, " + "gs: 0x%lx, ip: 0x%lx, cs: 0x%lx, flags: 0x%lx, sp: 0x%lx, ss: 0x%lx}\n", + regs->ax, regs->orig_ax, regs->bx, regs->cx, regs->dx, + regs->si, regs->di, regs->bp, regs->ds, regs->es, regs->fs, + regs->gs, regs->ip, regs->cs, regs->flags, regs->sp, regs->ss); +#elif defined(CONFIG_X86_64) + /* x86_64 pt_regs doesn't have ds, es, fs or gs. */ + snprintf(str, sizeof(str), + "{ax: 0x%lx, orig_ax: 0x%lx, bx: 0x%lx, cx: 0x%lx, dx: 0x%lx, " + "si: 0x%lx, di: 0x%lx, r8: 0x%lx, r9: 0x%lx, r10: 0x%lx, r11: 0x%lx, " + "r12: 0x%lx, r13: 0x%lx, r14: 0x%lx, r15: 0x%lx, bp: 0x%lx, ip: 0x%lx, " + "cs: 0x%lx, flags: 0x%lx, sp: 0x%lx, ss: 0x%lx}\n", + regs->ax, regs->orig_ax, regs->bx, regs->cx, regs->dx, + regs->si, regs->di, regs->r8, regs->r9, regs->r10, regs->r11, + regs->r12, regs->r13, regs->r14, regs->r15, regs->bp, regs->ip, + regs->cs, regs->flags, regs->sp, regs->ss); +#endif + setsvalue(ra, kp_tstring_new_local(ks, str)); +} +#endif + +/***************************/ +/* This definition should keep update with kernel/trace/trace.h */ +struct ftrace_event_field { + struct list_head link; + const char *name; + const char *type; + int filter_type; + int offset; + int size; + int is_signed; +}; + +static struct list_head *ktap_get_fields(struct ftrace_event_call *event_call) +{ + if (!event_call->class->get_fields) + return &event_call->class->fields; + return event_call->class->get_fields(event_call); +} + +static void get_field_value(ktap_state *ks, struct ktap_event *e, + struct ftrace_event_field *field, ktap_value *ra) +{ + void *value = (unsigned char *)e->entry + field->offset; + + if (field->size == 4) { + int n = *(int *)value; + setnvalue(ra, n); + return; + } else if (field->size == 8) { + long n = *(long *)value; + setnvalue(ra, n); + return; + } + + if (!strncmp(field->type, "char", 4)) { + setsvalue(ra, kp_tstring_new(ks, (char *)value)); + return; + } +} + +void kp_event_getarg(ktap_state *ks, ktap_value *ra, int n) +{ + struct ktap_event *e = ks->current_event; + int index = n; + struct ftrace_event_field *field; + struct list_head *head; + + /* this is very slow and not safe, fix it in future */ + head = ktap_get_fields(e->call); + list_for_each_entry_reverse(field, head, link) { + if (--index == 0) { + get_field_value(ks, e, field, ra); + return; + } + } + + setnilvalue(ra); + return; +} + +/* Callback function for perf event subsystem + * make ktap reentrant, don't disable irq in callback function, + * same as perf and ftrace. to make reentrant, we need some + * percpu data to be context isolation(irq/sirq/nmi/process) + * + * perf callback already consider on the recursion issue, + * so ktap don't need to check again in here. + * + * Note tracepoint handler is calling with rcu_read_lock. + */ +static void ktap_overflow_callback(struct perf_event *event, + struct perf_sample_data *data, + struct pt_regs *regs) +{ + struct ktap_probe_event *ktap_pevent; + struct ktap_event e; + ktap_state *ks; + int rctx; + + ktap_pevent = event->overflow_handler_context; + ks = ktap_pevent->ks; + + if (unlikely(ks->stop)) + return; + + rctx = get_recursion_context(); + if (rctx < 0) + return; + + /* profile perf event don't have valid associated tp_event */ + if (event->tp_event) { + e.call = event->tp_event; + e.entry = data->raw->data; + e.entry_size = data->raw->size; + } + e.pevent = ktap_pevent; + e.regs = regs; + + ktap_call_probe_closure(ks, ktap_pevent->cl, &e); + + put_recursion_context(rctx); +} + +static void perf_destructor(struct ktap_probe_event *ktap_pevent) +{ + perf_event_release_kernel(ktap_pevent->perf); +} + +static int (*kp_ftrace_profile_set_filter)(struct perf_event *event, + int event_id, char *filter_str); + +/* + * Generic perf event register function + * used by tracepoints/kprobe/uprobe/profile-timer/hw_breakpoint. + */ +void kp_perf_event_register(ktap_state *ks, struct perf_event_attr *attr, + struct task_struct *task, char *filter, + ktap_closure *cl) +{ + struct ktap_probe_event *ktap_pevent; + struct perf_event *event; + int cpu, ret; + + kp_verbose_printf(ks, "enable perf event id: %d, filter: %s " + "pid: %d\n", attr->config, filter, + task ? task_tgid_vnr(task) : -1); + + /* + * don't tracing until ktap_wait, the reason is: + * 1). some event may hit before apply filter + * 2). more simple to manage tracing thread + * 3). avoid race with mainthread. + * + * Another way to do this is make attr.disabled as 1, then use + * perf_event_enable after filter apply, however, perf_event_enable + * was not exported in kernel older than 3.3, so we drop this method. + */ + ks->stop = 1; + + for_each_cpu(cpu, G(ks)->cpumask) { + ktap_pevent = kp_zalloc(ks, sizeof(*ktap_pevent)); + ktap_pevent->ks = ks; + ktap_pevent->cl = cl; + event = perf_event_create_kernel_counter(attr, cpu, task, + ktap_overflow_callback, + ktap_pevent); + if (IS_ERR(event)) { + int err = PTR_ERR(event); + kp_error(ks, "unable register perf event %d on cpu %d, " + "err: %d\n", attr->config, cpu, err); + kp_free(ks, ktap_pevent); + return; + } + + ktap_pevent->perf = event; + INIT_LIST_HEAD(&ktap_pevent->list); + list_add_tail(&ktap_pevent->list, &G(ks)->probe_events_head); + + if (!filter) + continue; + + ret = kp_ftrace_profile_set_filter(event, attr->config, filter); + if (ret) { + kp_error(ks, "unable set filter %s for event id %d, " + "ret: %d\n", filter, attr->config, ret); + perf_destructor(ktap_pevent); + list_del(&ktap_pevent->list); + kp_free(ks, ktap_pevent); + return; + } + } +} + +static void end_probes(struct ktap_state *ks) +{ + struct ktap_probe_event *ktap_pevent; + struct list_head *tmp, *pos; + struct list_head *head = &G(ks)->probe_events_head; + + list_for_each(pos, head) { + ktap_pevent = container_of(pos, struct ktap_probe_event, + list); + perf_destructor(ktap_pevent); + } + /* + * Ensure our callback won't be called anymore. The buffers + * will be freed after that. + */ + tracepoint_synchronize_unregister(); + + list_for_each_safe(pos, tmp, head) { + ktap_pevent = container_of(pos, struct ktap_probe_event, + list); + list_del(&ktap_pevent->list); + kp_free(ks, ktap_pevent); + } +} + +static int ktap_lib_probe_by_id(ktap_state *ks) +{ + const char *ids_str; + char *start; + ktap_closure *cl; + struct task_struct *task = G(ks)->trace_task; + char filter_str[128] = {0}; + char *filter, *ptr1, *sep, *ptr; + + kp_arg_check(ks, 1, KTAP_TSTRING); + kp_arg_check(ks, 2, KTAP_TFUNCTION); + + ids_str = svalue(kp_arg(ks, 1)); + cl = clvalue(kp_arg(ks, 2)); + + start = (char *)ids_str; + + again: + filter = NULL; + + sep = strchr(start, ','); + if (!sep) + ptr1 = strchr(start, '/'); + else + ptr1 = strnchr(start, sep - start, '/'); + + if (ptr1) { + char *ptr2 = strrchr(ptr1 + 1, '/'); + + if (ptr2) { + memset(filter_str, 0, sizeof(filter_str)); + strncpy(filter_str, ptr1 + 1, ptr2 - ptr1 - 1); + filter = &filter_str[0]; + } else { + kp_printf(ks, "cannot parse ids_str: %s\n", ids_str); + return -1; + } + } + + for (ptr = start; *ptr != ',' && *ptr != '\0' && *ptr != '/'; ptr++) { + char token[32] = {0}; + int id; + int i = 0; + + if (*ptr == ' ') + continue; + + while (isdigit(*ptr)) { + token[i++] = *ptr++; + } + + if (!kstrtoint(token, 10, &id)) { + struct perf_event_attr attr; + + memset(&attr, 0, sizeof(attr)); + attr.type = PERF_TYPE_TRACEPOINT; + attr.config = id; + attr.sample_type = PERF_SAMPLE_RAW | PERF_SAMPLE_TIME | + PERF_SAMPLE_CPU | PERF_SAMPLE_PERIOD; + attr.sample_period = 1; + attr.size = sizeof(attr); + attr.disabled = 0; + + kp_perf_event_register(ks, &attr, task, filter, cl); + } + } + + if (sep && (*(sep + 1) != '\0')) { + start = sep + 1; + goto again; + } + + return 0; +} + +static int ktap_lib_probe_end(ktap_state *ks) +{ + kp_arg_check(ks, 1, KTAP_TFUNCTION); + + G(ks)->trace_end_closure = clvalue(kp_arg(ks, 1)); + return 0; +} + +static int ktap_lib_traceoff(ktap_state *ks) +{ + end_probes(ks); + + /* call trace_end_closure after probed end */ + if (G(ks)->trace_end_closure) { + setcllvalue(ks->top, G(ks)->trace_end_closure); + incr_top(ks); + kp_call(ks, ks->top - 1, 0); + G(ks)->trace_end_closure = NULL; + } + + return 0; +} + +void kp_probe_exit(ktap_state *ks) +{ + if (!G(ks)->trace_enabled) + return; + + end_probes(ks); + + /* call trace_end_closure after probed end */ + if (!G(ks)->error && G(ks)->trace_end_closure) { + setcllvalue(ks->top, G(ks)->trace_end_closure); + incr_top(ks); + kp_call(ks, ks->top - 1, 0); + G(ks)->trace_end_closure = NULL; + } + + G(ks)->trace_enabled = 0; +} + +int kp_probe_init(ktap_state *ks) +{ + G(ks)->trace_enabled = 1; + return 0; +} + +static const ktap_Reg kdebuglib_funcs[] = { + {"probe_by_id", ktap_lib_probe_by_id}, + {"probe_end", ktap_lib_probe_end}, + {"traceoff", ktap_lib_traceoff}, + {NULL} +}; + +void kp_init_kdebuglib(ktap_state *ks) +{ + kp_ftrace_profile_set_filter = + (void *)kallsyms_lookup_name("ftrace_profile_set_filter"); + if (!kp_ftrace_profile_set_filter) { + printk("ktap: cannot lookup ftrace_profile_set_filter " + "in kallsyms\n"); + return; + } + + kp_register_lib(ks, "kdebug", kdebuglib_funcs); +} + diff --git a/drivers/staging/ktap/interpreter/library/timer.c b/drivers/staging/ktap/interpreter/library/timer.c new file mode 100644 index 000000000000..759f917b3658 --- /dev/null +++ b/drivers/staging/ktap/interpreter/library/timer.c @@ -0,0 +1,192 @@ +/* + * timer.c - timer library support for ktap + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include "../../include/ktap.h" + +struct hrtimer_ktap { + struct hrtimer timer; + ktap_state *ks; + ktap_closure *cl; + u64 ns; + struct list_head list; +}; + +/* + * Currently ktap disallow tracing event in timer callback closure, + * that will corrupt ktap_state and ktap stack, because timer closure + * and event closure use same irq percpu ktap_state and stack. + * We can use a different percpu ktap_state and stack for timer purpuse, + * but that's don't bring any big value with cost on memory consuming. + * + * So just simply disable tracing in timer closure, + * get_recursion_context()/put_recursion_context() is used for this purpose. + * + * option: export perf_swevent_put_recursion_context to slove this issue. + */ +static enum hrtimer_restart hrtimer_ktap_fn(struct hrtimer *timer) +{ + struct hrtimer_ktap *t; + ktap_state *ks; + int rctx; + + rcu_read_lock_sched_notrace(); + rctx = get_recursion_context(); + + t = container_of(timer, struct hrtimer_ktap, timer); + + ks = kp_newthread(t->ks); + setcllvalue(ks->top, t->cl); + incr_top(ks); + kp_call(ks, ks->top - 1, 0); + kp_exitthread(ks); + + hrtimer_add_expires_ns(timer, t->ns); + + put_recursion_context(rctx); + rcu_read_unlock_sched_notrace(); + + return HRTIMER_RESTART; +} + +static void set_tick_timer(ktap_state *ks, u64 period, ktap_closure *cl) +{ + struct hrtimer_ktap *t; + + t = kp_malloc(ks, sizeof(*t)); + t->ks = ks; + t->cl = cl; + t->ns = period; + + INIT_LIST_HEAD(&t->list); + list_add(&t->list, &(G(ks)->timers)); + + hrtimer_init(&t->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + t->timer.function = hrtimer_ktap_fn; + hrtimer_start(&t->timer, ns_to_ktime(period), HRTIMER_MODE_REL); +} + +static void set_profile_timer(ktap_state *ks, u64 period, ktap_closure *cl) +{ + struct perf_event_attr attr; + + memset(&attr, 0, sizeof(attr)); + attr.type = PERF_TYPE_SOFTWARE; + attr.config = PERF_COUNT_SW_CPU_CLOCK; + attr.sample_type = PERF_SAMPLE_RAW | PERF_SAMPLE_TIME | + PERF_SAMPLE_CPU | PERF_SAMPLE_PERIOD; + attr.sample_period = period; + attr.size = sizeof(attr); + attr.disabled = 0; + + kp_perf_event_register(ks, &attr, NULL, NULL, cl); +} + +static int do_tick_profile(ktap_state *ks, int is_tick) +{ + const char *str, *tmp; + char interval_str[32] = {0}; + char suffix[10] = {0}; + int n, i = 0; + int factor; + + kp_arg_check(ks, 1, KTAP_TSTRING); + kp_arg_check(ks, 2, KTAP_TFUNCTION); + + str = svalue(kp_arg(ks, 1)); + tmp = str; + while (isdigit(*tmp)) + tmp++; + + strncpy(interval_str, str, tmp - str); + if (kstrtoint(interval_str, 10, &n)) + goto error; + + strncpy(suffix, tmp, 9); + while (suffix[i] != ' ' && suffix[i] != '\0') + i++; + + suffix[i] = '\0'; + + if (!strcmp(suffix, "s") || !strcmp(suffix, "sec")) + factor = NSEC_PER_SEC; + else if (!strcmp(suffix, "ms") || !strcmp(suffix, "msec")) + factor = NSEC_PER_MSEC; + else if (!strcmp(suffix, "us") || !strcmp(suffix, "usec")) + factor = NSEC_PER_USEC; + else + goto error; + + if (is_tick) + set_tick_timer(ks, (u64)factor * n, clvalue(kp_arg(ks, 2))); + else + set_profile_timer(ks, (u64)factor * n, clvalue(kp_arg(ks, 2))); + + return 0; + + error: + kp_error(ks, "cannot parse timer interval: %s\n", str); + return -1; +} + +/* + * tick-n probes fire on only one CPU per interval. + * valid time suffixes: sec/s, msec/ms, usec/us + */ +static int ktap_lib_tick(ktap_state *ks) +{ + return do_tick_profile(ks, 1); +} + +/* + * A profile-n probe fires every fixed interval on every CPU + * valid time suffixes: sec/s, msec/ms, usec/us + */ +static int ktap_lib_profile(ktap_state *ks) +{ + return do_tick_profile(ks, 0); +} + +void kp_exit_timers(ktap_state *ks) +{ + struct hrtimer_ktap *t, *tmp; + struct list_head *timers_list = &(G(ks)->timers); + + list_for_each_entry_safe(t, tmp, timers_list, list) { + hrtimer_cancel(&t->timer); + kp_free(ks, t); + } +} + +static const ktap_Reg timerlib_funcs[] = { + {"profile", ktap_lib_profile}, + {"tick", ktap_lib_tick}, + {NULL} +}; + +void kp_init_timerlib(ktap_state *ks) +{ + kp_register_lib(ks, "timer", timerlib_funcs); +} + diff --git a/drivers/staging/ktap/interpreter/loader.c b/drivers/staging/ktap/interpreter/loader.c new file mode 100644 index 000000000000..0da54d7ef146 --- /dev/null +++ b/drivers/staging/ktap/interpreter/loader.c @@ -0,0 +1,310 @@ +/* + * loader.c - loader for ktap bytecode chunk file + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * Copyright (C) 1994-2013 Lua.org, PUC-Rio. + * - The part of code in this file is copied from lua initially. + * - lua's MIT license is compatible with GPL. + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include "../include/ktap.h" + +#define KTAPC_TAIL "\x19\x93\r\n\x1a\n" + +struct load_state { + unsigned char *buff; + int pos; + ktap_state *ks; +}; + +#define READ_CHAR(S) (S->buff[S->pos++]) +#define READ_BYTE(S) READ_CHAR(S) +#define READ_INT(S) load_int(S) +#define READ_NUMBER(S) load_number(S) +#define READ_STRING(S) load_string(S) +#define READ_VECTOR(S, dst, size) \ + do { \ + memcpy(dst, &S->buff[S->pos], size); \ + S->pos += size; \ + } while(0) + +#define NEW_VECTOR(S, size) kp_malloc(S->ks, size) +#define GET_CURRENT(S) &S->buff[S->pos] +#define ADD_POS(S, size) S->pos += size + + +static int load_function(struct load_state *S, ktap_proto *f); + + +static int load_int(struct load_state *S) +{ + int x; + + READ_VECTOR(S, &x, sizeof(int)); + return x; +} + +static int load_number(struct load_state *S) +{ + int x; + + READ_VECTOR(S, &x, sizeof(ktap_number)); + return x; +} + +static ktap_string *load_string(struct load_state *S) +{ + ktap_string *ts; + size_t size; + + size = READ_INT(S); + + if (!size) + return NULL; + else { + char *s = GET_CURRENT(S); + ADD_POS(S, size); + /* remove trailing '\0' */ + ts = kp_tstring_newlstr(S->ks, s, size - 1); + return ts; + } +} + + +static int load_code(struct load_state *S, ktap_proto *f) +{ + int n = READ_INT(S); + + f->sizecode = n; + f->code = NEW_VECTOR(S, n * sizeof(ktap_instruction)); + READ_VECTOR(S, f->code, n * sizeof(ktap_instruction)); + + return 0; +} + +static int load_constants(struct load_state *S, ktap_proto *f) +{ + int i,n; + + n = READ_INT(S); + + f->sizek = n; + f->k = NEW_VECTOR(S, n * sizeof(ktap_value)); + for (i = 0; i < n; i++) + setnilvalue(&f->k[i]); + + for (i=0; i < n; i++) { + ktap_value *o = &f->k[i]; + + int t = READ_CHAR(S); + switch (t) { + case KTAP_TNIL: + setnilvalue(o); + break; + case KTAP_TBOOLEAN: + setbvalue(o, READ_CHAR(S)); + break; + case KTAP_TNUMBER: + /* + * todo: kernel not support fp, check double when + * loading + */ + setnvalue(o, READ_NUMBER(S)); + break; + case KTAP_TSTRING: + setsvalue(o, READ_STRING(S)); + break; + default: + kp_error(S->ks, "ktap: load_constants: " + "unknow ktap_value\n"); + return -1; + + } + } + + n = READ_INT(S); + f->p = NEW_VECTOR(S, n * sizeof(ktap_proto)); + f->sizep = n; + for (i = 0; i < n; i++) + f->p[i] = NULL; + for (i = 0; i < n; i++) { + f->p[i] = kp_newproto(S->ks); + if (load_function(S, f->p[i])) + return -1; + } + + return 0; +} + + +static int load_upvalues(struct load_state *S, ktap_proto *f) +{ + int i,n; + + n = READ_INT(S); + f->upvalues = NEW_VECTOR(S, n * sizeof(ktap_upvaldesc)); + f->sizeupvalues = n; + + for (i = 0; i < n; i++) + f->upvalues[i].name = NULL; + + for (i = 0; i < n; i++) { + f->upvalues[i].instack = READ_BYTE(S); + f->upvalues[i].idx = READ_BYTE(S); + } + + return 0; +} + +static int load_debuginfo(struct load_state *S, ktap_proto *f) +{ + int i,n; + + f->source = READ_STRING(S); + n = READ_INT(S); + f->sizelineinfo = n; + f->lineinfo = NEW_VECTOR(S, n * sizeof(int)); + READ_VECTOR(S, f->lineinfo, n * sizeof(int)); + n = READ_INT(S); + f->locvars = NEW_VECTOR(S, n * sizeof(struct ktap_locvar)); + f->sizelocvars = n; + for (i = 0; i < n; i++) + f->locvars[i].varname = NULL; + for (i = 0; i < n; i++) { + f->locvars[i].varname = READ_STRING(S); + f->locvars[i].startpc = READ_INT(S); + f->locvars[i].endpc = READ_INT(S); + } + n = READ_INT(S); + for (i = 0; i < n; i++) + f->upvalues[i].name = READ_STRING(S); + + return 0; +} + +static int load_function(struct load_state *S, ktap_proto *f) +{ + f->linedefined = READ_INT(S); + f->lastlinedefined = READ_INT(S); + f->numparams = READ_BYTE(S); + f->is_vararg = READ_BYTE(S); + f->maxstacksize = READ_BYTE(S); + if (load_code(S, f)) + return -1; + if (load_constants(S, f)) + return -1; + if (load_upvalues(S, f)) + return -1; + if (load_debuginfo(S, f)) + return -1; + + return 0; +} + + +#define error(S, why) \ + kp_error(S->ks, "load failed: %s precompiled chunk\n", why) + +#define N0 KTAPC_HEADERSIZE +#define N1 (sizeof(KTAP_SIGNATURE) - sizeof(char)) +#define N2 N1 + 2 +#define N3 N2 + 6 + +static int load_header(struct load_state *S) +{ + u8 h[KTAPC_HEADERSIZE]; + u8 s[KTAPC_HEADERSIZE]; + + kp_header(h); + READ_VECTOR(S, s, KTAPC_HEADERSIZE); + + if (memcmp(h, s, N0) == 0) + return 0; + if (memcmp(h, s, N1) != 0) + error(S, "not a"); + else if (memcmp(h, s, N2) != 0) + error(S, "version mismatch in"); + else if (memcmp(h, s, N3) != 0) + error(S, "incompatible"); + else + error(S,"corrupted"); + + return -1; +} + + +static int verify_code(struct load_state *S, ktap_proto *f) +{ + /* not support now */ + return 0; +} + + +ktap_closure *kp_load(ktap_state *ks, unsigned char *buff) +{ + struct load_state S; + ktap_closure *cl; + ktap_lclosure *f; + int ret, i; + + S.ks = ks; + S.buff = buff; + S.pos = 0; + + ret = load_header(&S); + if (ret) + return NULL; + + cl = kp_newlclosure(ks, 1); + if (!cl) + return cl; + + /* put closure on the top, prepare to run with this closure */ + setcllvalue(ks->top, cl); + incr_top(ks); + + cl->l.p = kp_newproto(ks); + if (load_function(&S, cl->l.p)) + return NULL; + + if (cl->l.p->sizeupvalues != 1) { + ktap_proto *p = cl->l.p; + cl = kp_newlclosure(ks, cl->l.p->sizeupvalues); + cl->l.p = p; + setcllvalue(ks->top - 1, cl); + } + + f = &cl->l; + for (i = 0; i < f->nupvalues; i++) { /* initialize upvalues */ + ktap_upval *up = kp_newupval(ks); + f->upvals[i] = up; + } + + /* set global table as 1st upvalue of 'f' */ + if (f->nupvalues == 1) { + ktap_table *reg = hvalue(&G(ks)->registry); + const ktap_value *gt = kp_table_getint(reg, KTAP_RIDX_GLOBALS); + setobj(f->upvals[0]->v, gt); + } + + verify_code(&S, cl->l.p); + return cl; +} + diff --git a/drivers/staging/ktap/interpreter/object.c b/drivers/staging/ktap/interpreter/object.c new file mode 100644 index 000000000000..6538167a2b87 --- /dev/null +++ b/drivers/staging/ktap/interpreter/object.c @@ -0,0 +1,483 @@ +/* + * object.c - ktap object generic operation + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * Copyright (C) 1994-2013 Lua.org, PUC-Rio. + * - The part of code in this file is copied from lua initially. + * - lua's MIT license is compatible with GPL. + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef __KERNEL__ +#include "../include/ktap.h" +#else +#include "../include/ktap_types.h" +#endif + +#ifdef __KERNEL__ + +#define KTAP_ALLOC_FLAGS ((GFP_KERNEL | __GFP_NORETRY | __GFP_NOWARN) \ + & ~__GFP_WAIT) + +void *kp_malloc(ktap_state *ks, int size) +{ + void *addr; + + /* + * Normally we don't want to trace under memory pressure, + * so we use a simple rule to handle memory allocation failure: + * + * retry until allocation success, this will make caller don't need + * to handle the unlikely failure case, then ktap exit. + * + * In this approach, if user find there have memory allocation failure, + * user should re-run the ktap script, or fix the memory pressure + * issue, or figure out why the script need so many memory. + * + * Perhaps return pre-allocated stub memory trunk when allocate failed + * is a better approch? + */ + addr = kmalloc(size, KTAP_ALLOC_FLAGS); + if (unlikely(!addr)) { + kp_error(ks, "kmalloc size %d failed, retry again\n", size); + printk("ktap kmalloc size %d failed, retry again\n", size); + dump_stack(); + while (1) { + addr = kmalloc(size, KTAP_ALLOC_FLAGS); + if (addr) + break; + } + kp_printf(ks, "kmalloc retry success after failed, exit\n"); + } + + return addr; +} + +void kp_free(ktap_state *ks, void *addr) +{ + kfree(addr); +} + +void *kp_reallocv(ktap_state *ks, void *addr, int oldsize, int newsize) +{ + void *new_addr; + + new_addr = krealloc(addr, newsize, KTAP_ALLOC_FLAGS); + if (unlikely(!new_addr)) { + kp_error(ks, "krealloc size %d failed, retry again\n", newsize); + printk("ktap krealloc size %d failed, retry again\n", newsize); + dump_stack(); + while (1) { + new_addr = krealloc(addr, newsize, KTAP_ALLOC_FLAGS); + if (new_addr) + break; + } + kp_printf(ks, "krealloc retry success after failed, exit\n"); + } + + return new_addr; +} + +void *kp_zalloc(ktap_state *ks, int size) +{ + void *addr; + + addr = kzalloc(size, KTAP_ALLOC_FLAGS); + if (unlikely(!addr)) { + kp_error(ks, "kzalloc size %d failed, retry again\n", size); + printk("ktap kzalloc size %d failed, retry again\n", size); + dump_stack(); + while (1) { + addr = kzalloc(size, KTAP_ALLOC_FLAGS); + if (addr) + break; + } + kp_printf(ks, "kzalloc retry success after failed, exit\n"); + } + + return addr; +} +#endif + +void kp_obj_dump(ktap_state *ks, const ktap_value *v) +{ + switch (ttype(v)) { + case KTAP_TNIL: + kp_puts(ks, "NIL"); + break; + case KTAP_TNUMBER: + kp_printf(ks, "NUMBER %ld", nvalue(v)); + break; + case KTAP_TBOOLEAN: + kp_printf(ks, "BOOLEAN %d", bvalue(v)); + break; + case KTAP_TLIGHTUSERDATA: + kp_printf(ks, "LIGHTUSERDATA 0x%lx", (unsigned long)pvalue(v)); + break; + case KTAP_TLCF: + kp_printf(ks, "LIGHTCFCUNTION 0x%lx", (unsigned long)fvalue(v)); + break; + case KTAP_TSHRSTR: + case KTAP_TLNGSTR: + kp_printf(ks, "SHRSTR #%s", svalue(v)); + break; + case KTAP_TUSERDATA: + kp_printf(ks, "USERDATA 0x%lx", (unsigned long)uvalue(v)); + break; + case KTAP_TTABLE: + kp_printf(ks, "TABLE 0x%lx", (unsigned long)hvalue(v)); + break; + default: + kp_printf(ks, "GCVALUE 0x%lx", (unsigned long)gcvalue(v)); + break; + } +} + +#ifdef __KERNEL__ +#include +#include + +static void kp_btrace_dump(ktap_state *ks, ktap_btrace *bt) +{ + char str[KSYM_SYMBOL_LEN]; + int i; + + for (i = 0; i < bt->nr_entries; i++) { + unsigned long p = bt->entries[i]; + + if (p == ULONG_MAX) + break; + + SPRINT_SYMBOL(str, p); + kp_printf(ks, "%s\n", str); + } +} + +static int kp_btrace_equal(ktap_btrace *bt1, ktap_btrace *bt2) +{ + int i; + + if (bt1->nr_entries != bt2->nr_entries) + return 0; + + for (i = 0; i < bt1->nr_entries; i++) { + if (bt1->entries[i] != bt2->entries[i]) + return 0; + } + + return 1; +} +#endif + +void kp_showobj(ktap_state *ks, const ktap_value *v) +{ + switch (ttype(v)) { + case KTAP_TNIL: + kp_puts(ks, "nil"); + break; + case KTAP_TNUMBER: + kp_printf(ks, "%ld", nvalue(v)); + break; + case KTAP_TBOOLEAN: + kp_puts(ks, (bvalue(v) == 1) ? "true" : "false"); + break; + case KTAP_TLIGHTUSERDATA: + kp_printf(ks, "0x%lx", (unsigned long)pvalue(v)); + break; + case KTAP_TLCF: + kp_printf(ks, "0x%lx", (unsigned long)fvalue(v)); + break; + case KTAP_TSHRSTR: + case KTAP_TLNGSTR: + kp_puts(ks, svalue(v)); + break; + case KTAP_TUSERDATA: + kp_printf(ks, "0x%lx", (unsigned long)uvalue(v)); + break; + case KTAP_TTABLE: + kp_table_dump(ks, hvalue(v)); + break; +#ifdef __KERNEL__ + case KTAP_TEVENT: + kp_transport_event_write(ks, evalue(v)); + break; + case KTAP_TBTRACE: + kp_btrace_dump(ks, btvalue(v)); + break; + case KTAP_TAGGRTABLE: + kp_aggrtable_dump(ks, ahvalue(v)); + break; + case KTAP_TAGGRACCVAL: + kp_aggraccval_dump(ks, aggraccvalue(v)); + break; +#endif + default: + kp_error(ks, "print unknown value type: %d\n", ttype(v)); + break; + } +} + + +/* + * equality of ktap values. ks == NULL means raw equality + */ +int kp_equalobjv(ktap_state *ks, const ktap_value *t1, const ktap_value *t2) +{ + switch (ttype(t1)) { + case KTAP_TNIL: + return 1; + case KTAP_TNUMBER: + return nvalue(t1) == nvalue(t2); + case KTAP_TBOOLEAN: + return bvalue(t1) == bvalue(t2); /* true must be 1 !! */ + case KTAP_TLIGHTUSERDATA: + return pvalue(t1) == pvalue(t2); + case KTAP_TLCF: + return fvalue(t1) == fvalue(t2); + case KTAP_TSHRSTR: + return eqshrstr(rawtsvalue(t1), rawtsvalue(t2)); + case KTAP_TLNGSTR: + return kp_tstring_eqlngstr(rawtsvalue(t1), rawtsvalue(t2)); + case KTAP_TUSERDATA: + if (uvalue(t1) == uvalue(t2)) + return 1; + else if (ks == NULL) + return 0; + case KTAP_TTABLE: + if (hvalue(t1) == hvalue(t2)) + return 1; + else if (ks == NULL) + return 0; +#ifdef __KERNEL__ + case KTAP_TBTRACE: + return kp_btrace_equal(btvalue(t1), btvalue(t2)); +#endif + default: + return gcvalue(t1) == gcvalue(t2); + } + + return 0; +} + +/* + * ktap will not use lua's length operator on table meaning, + * also # is not for length operator any more in ktap. + * + * Quote from lua mannal: + * 2.5.5 - The Length Operator + * + * The length operator is denoted by the unary operator #. + * The length of a string is its number of bytes(that is, + * the usual meaning of string length when each character is one byte). + * + * The length of a table t is defined to be any integer index n + * such that t[n] is not nil and t[n+1] is nil; moreover, if t[1] is nil, + * n can be zero. For a regular array, with non-nil values from 1 to a given n, + * its length is exactly that n, the index of its last value. If the array has + * "holes" (that is, nil values between other non-nil values), then #t can be + * any of the indices that directly precedes a nil value + * (that is, it may consider any such nil value as the end of the array). + */ +int kp_objlen(ktap_state *ks, const ktap_value *v) +{ + switch(v->type) { + case KTAP_TTABLE: + return kp_table_length(ks, hvalue(v)); + case KTAP_TSTRING: + return rawtsvalue(v)->tsv.len; + default: + kp_printf(ks, "cannot get length of type %d\n", v->type); + return -1; + } + return 0; +} + +/* need to protect allgc field? */ +ktap_gcobject *kp_newobject(ktap_state *ks, int type, size_t size, + ktap_gcobject **list) +{ + ktap_gcobject *o; + + o = kp_malloc(ks, size); + if (list == NULL) + list = &G(ks)->allgc; + + gch(o)->tt = type; + gch(o)->next = *list; + *list = o; + + return o; +} + +ktap_upval *kp_newupval(ktap_state *ks) +{ + ktap_upval *uv; + + uv = &kp_newobject(ks, KTAP_TUPVAL, sizeof(ktap_upval), NULL)->uv; + uv->v = &uv->u.value; + setnilvalue(uv->v); + return uv; +} + +static ktap_btrace *kp_newbacktrace(ktap_state *ks, ktap_gcobject **list) +{ + ktap_btrace *bt; + + bt = &kp_newobject(ks, KTAP_TBTRACE, sizeof(ktap_btrace), list)->bt; + return bt; +} + +void kp_objclone(ktap_state *ks, const ktap_value *o, ktap_value *newo, + ktap_gcobject **list) +{ + if (ttisbtrace(o)) { + ktap_btrace *bt; + bt = kp_newbacktrace(ks, list); + bt->nr_entries = btvalue(o)->nr_entries; + memcpy(&bt->entries[0], &btvalue(o)->entries[0], + sizeof(bt->entries)); + setbtvalue(newo, bt); + } else { + kp_error(ks, "cannot clone ktap value type %d\n", ttype(o)); + setnilvalue(newo); + } +} + +ktap_closure *kp_newlclosure(ktap_state *ks, int n) +{ + ktap_closure *cl; + + cl = (ktap_closure *)kp_newobject(ks, KTAP_TLCL, sizeof(*cl), NULL); + cl->l.p = NULL; + cl->l.nupvalues = n; + while (n--) + cl->l.upvals[n] = NULL; + + return cl; +} + +static void free_proto(ktap_state *ks, ktap_proto *f) +{ + kp_free(ks, f->code); + kp_free(ks, f->p); + kp_free(ks, f->k); + kp_free(ks, f->lineinfo); + kp_free(ks, f->locvars); + kp_free(ks, f->upvalues); + kp_free(ks, f); +} + +ktap_proto *kp_newproto(ktap_state *ks) +{ + ktap_proto *f; + f = (ktap_proto *)kp_newobject(ks, KTAP_TPROTO, sizeof(*f), NULL); + f->k = NULL; + f->sizek = 0; + f->p = NULL; + f->sizep = 0; + f->code = NULL; + f->cache = NULL; + f->sizecode = 0; + f->lineinfo = NULL; + f->sizelineinfo = 0; + f->upvalues = NULL; + f->sizeupvalues = 0; + f->numparams = 0; + f->is_vararg = 0; + f->maxstacksize = 0; + f->locvars = NULL; + f->sizelocvars = 0; + f->linedefined = 0; + f->lastlinedefined = 0; + f->source = NULL; + return f; +} + +static ktap_udata *newudata(ktap_state *ks, size_t s) +{ + ktap_udata *u; + + u = &kp_newobject(ks, KTAP_TUSERDATA, sizeof(ktap_udata) + s, NULL)->u; + u->uv.len = s; + return u; +} + +void *kp_newuserdata(ktap_state *ks, size_t size) +{ + ktap_udata *u; + + u = newudata(ks, size); + return u + 1; +} + +void kp_free_gclist(ktap_state *ks, ktap_gcobject *o) +{ + while (o) { + ktap_gcobject *next; + + next = gch(o)->next; + switch (gch(o)->tt) { + case KTAP_TTABLE: + kp_table_free(ks, (ktap_table *)o); + break; + case KTAP_TPROTO: + free_proto(ks, (ktap_proto *)o); + break; +#ifdef __KERNEL__ + case KTAP_TAGGRTABLE: + kp_aggrtable_free(ks, (ktap_aggrtable *)o); + break; +#endif + default: + kp_free(ks, o); + } + o = next; + } +} + +void kp_free_all_gcobject(ktap_state *ks) +{ + kp_free_gclist(ks, G(ks)->allgc); + G(ks)->allgc = NULL; +} + +/******************************************************************************/ + +/* + * make header for precompiled chunks + * if you change the code below be sure to update load_header and FORMAT above + * and KTAPC_HEADERSIZE in ktap_types.h + */ +void kp_header(u8 *h) +{ + int x = 1; + + memcpy(h, KTAP_SIGNATURE, sizeof(KTAP_SIGNATURE) - sizeof(char)); + h += sizeof(KTAP_SIGNATURE) - sizeof(char); + *h++ = (u8)VERSION; + *h++ = (u8)FORMAT; + *h++ = (u8)(*(char*)&x); /* endianness */ + *h++ = (u8)(sizeof(int)); + *h++ = (u8)(sizeof(size_t)); + *h++ = (u8)(sizeof(ktap_instruction)); + *h++ = (u8)(sizeof(ktap_number)); + *h++ = (u8)(((ktap_number)0.5) == 0); /* is ktap_number integral? */ + memcpy(h, KTAPC_TAIL, sizeof(KTAPC_TAIL) - sizeof(char)); +} + + diff --git a/drivers/staging/ktap/interpreter/opcode.c b/drivers/staging/ktap/interpreter/opcode.c new file mode 100644 index 000000000000..2f44855fcad2 --- /dev/null +++ b/drivers/staging/ktap/interpreter/opcode.c @@ -0,0 +1,127 @@ +/* + * opcode.c + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * Copyright (C) 1994-2013 Lua.org, PUC-Rio. + * - The part of code in this file is copied from lua initially. + * - lua's MIT license is compatible with GPL. + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "../include/ktap_types.h" +#include "../include/ktap_opcodes.h" + +const char *const ktap_opnames[NUM_OPCODES + 1] = { + "MOVE", + "LOADK", + "LOADKX", + "LOADBOOL", + "LOADNIL", + "GETUPVAL", + "GETTABUP", + "GETTABLE", + "SETTABUP", + "SETTABUP_INCR", + "SETUPVAL", + "SETTABLE", + "SETTABLE_INCR", + "NEWTABLE", + "SELF", + "ADD", + "SUB", + "MUL", + "DIV", + "MOD", + "POW", + "UNM", + "NOT", + "LEN", + "CONCAT", + "JMP", + "EQ", + "LT", + "LE", + "TEST", + "TESTSET", + "CALL", + "TAILCALL", + "RETURN", + "FORLOOP", + "FORPREP", + "TFORCALL", + "TFORLOOP", + "SETLIST", + "CLOSURE", + "VARARG", + "EXTRAARG", + + "EVENT", + "EVENT_NAME", + "EVENT_ARG", /* arg1, arg2 .. arg9 */ + NULL +}; + + +#define opmode(t,a,b,c,m) (((t)<<7) | ((a)<<6) | ((b)<<4) | ((c)<<2) | (m)) + +const u8 ktap_opmodes[NUM_OPCODES] = { +/* T A B C mode opcode */ + opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_MOVE */ + ,opmode(0, 1, OpArgK, OpArgN, iABx) /* OP_LOADK */ + ,opmode(0, 1, OpArgN, OpArgN, iABx) /* OP_LOADKX */ + ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_LOADBOOL */ + ,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_LOADNIL */ + ,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_GETUPVAL */ + ,opmode(0, 1, OpArgU, OpArgK, iABC) /* OP_GETTABUP */ + ,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_GETTABLE */ + ,opmode(0, 0, OpArgK, OpArgK, iABC) /* OP_SETTABUP */ + ,opmode(0, 0, OpArgU, OpArgN, iABC) /* OP_SETUPVAL */ + ,opmode(0, 0, OpArgK, OpArgK, iABC) /* OP_SETTABLE */ + ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_NEWTABLE */ + ,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_SELF */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_ADD */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_SUB */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_MUL */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_DIV */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_MOD */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_POW */ + ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_UNM */ + ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_NOT */ + ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_LEN */ + ,opmode(0, 1, OpArgR, OpArgR, iABC) /* OP_CONCAT */ + ,opmode(0, 0, OpArgR, OpArgN, iAsBx) /* OP_JMP */ + ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_EQ */ + ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_LT */ + ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_LE */ + ,opmode(1, 0, OpArgN, OpArgU, iABC) /* OP_TEST */ + ,opmode(1, 1, OpArgR, OpArgU, iABC) /* OP_TESTSET */ + ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_CALL */ + ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_TAILCALL */ + ,opmode(0, 0, OpArgU, OpArgN, iABC) /* OP_RETURN */ + ,opmode(0, 1, OpArgR, OpArgN, iAsBx) /* OP_FORLOOP */ + ,opmode(0, 1, OpArgR, OpArgN, iAsBx) /* OP_FORPREP */ + ,opmode(0, 0, OpArgN, OpArgU, iABC) /* OP_TFORCALL */ + ,opmode(0, 1, OpArgR, OpArgN, iAsBx) /* OP_TFORLOOP */ + ,opmode(0, 0, OpArgU, OpArgU, iABC) /* OP_SETLIST */ + ,opmode(0, 1, OpArgU, OpArgN, iABx) /* OP_CLOSURE */ + ,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_VARARG */ + ,opmode(0, 0, OpArgU, OpArgU, iAx) /* OP_EXTRAARG */ + ,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_EVENT */ +}; + + diff --git a/drivers/staging/ktap/interpreter/strfmt.c b/drivers/staging/ktap/interpreter/strfmt.c new file mode 100644 index 000000000000..2c1e7618fef4 --- /dev/null +++ b/drivers/staging/ktap/interpreter/strfmt.c @@ -0,0 +1,196 @@ +/* + * strfmt.c - printf implementation + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * Copyright (C) 1994-2013 Lua.org, PUC-Rio. + * - The part of code in this file is copied from lua initially. + * - lua's MIT license is compatible with GPL. + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include "../include/ktap.h" + +/* macro to `unsign' a character */ +#define uchar(c) ((unsigned char)(c)) + +#define L_ESC '%' + +/* valid flags in a format specification */ +#define FLAGS "-+ #0" + +#define INTFRMLEN "ll" +#define INTFRM_T long long + +/* + * maximum size of each format specification (such as '%-099.99d') + * (+10 accounts for %99.99x plus margin of error) + */ +#define MAX_FORMAT (sizeof(FLAGS) + sizeof(INTFRMLEN) + 10) + +static const char *scanformat(ktap_state *ks, const char *strfrmt, char *form) +{ + const char *p = strfrmt; + while (*p != '\0' && strchr(FLAGS, *p) != NULL) + p++; /* skip flags */ + + if ((size_t)(p - strfrmt) >= sizeof(FLAGS)/sizeof(char)) { + kp_error(ks, "invalid format (repeated flags)\n"); + return NULL; + } + + if (isdigit(uchar(*p))) + p++; /* skip width */ + + if (isdigit(uchar(*p))) + p++; /* (2 digits at most) */ + + if (*p == '.') { + p++; + if (isdigit(uchar(*p))) + p++; /* skip precision */ + if (isdigit(uchar(*p))) + p++; /* (2 digits at most) */ + } + + if (isdigit(uchar(*p))) { + kp_error(ks, "invalid format (width or precision too long)\n"); + return NULL; + } + + *(form++) = '%'; + memcpy(form, strfrmt, (p - strfrmt + 1) * sizeof(char)); + form += p - strfrmt + 1; + *form = '\0'; + return p; +} + + +/* + * add length modifier into formats + */ +static void addlenmod(char *form, const char *lenmod) +{ + size_t l = strlen(form); + size_t lm = strlen(lenmod); + char spec = form[l - 1]; + + strcpy(form + l - 1, lenmod); + form[l + lm - 1] = spec; + form[l + lm] = '\0'; +} + + +static void ktap_argerror(ktap_state *ks, int narg, const char *extramsg) +{ + kp_error(ks, "bad argument #%d: (%s)\n", narg, extramsg); +} + +int kp_strfmt(ktap_state *ks, struct trace_seq *seq) +{ + int arg = 1; + size_t sfl; + ktap_value *arg_fmt = kp_arg(ks, 1); + int argnum = kp_arg_nr(ks); + const char *strfrmt, *strfrmt_end; + + strfrmt = svalue(arg_fmt); + sfl = rawtsvalue(arg_fmt)->tsv.len; + strfrmt_end = strfrmt + sfl; + + while (strfrmt < strfrmt_end) { + if (*strfrmt != L_ESC) + trace_seq_putc(seq, *strfrmt++); + else if (*++strfrmt == L_ESC) + trace_seq_putc(seq, *strfrmt++); + else { /* format item */ + char form[MAX_FORMAT]; + + if (++arg > argnum) { + ktap_argerror(ks, arg, "no value"); + return -1; + } + + strfrmt = scanformat(ks, strfrmt, form); + switch (*strfrmt++) { + case 'c': + trace_seq_printf(seq, form, + nvalue(kp_arg(ks, arg))); + break; + case 'd': case 'i': { + ktap_number n = nvalue(kp_arg(ks, arg)); + INTFRM_T ni = (INTFRM_T)n; + addlenmod(form, INTFRMLEN); + trace_seq_printf(seq, form, ni); + break; + } + case 'p': { + char str[KSYM_SYMBOL_LEN]; + SPRINT_SYMBOL(str, nvalue(kp_arg(ks, arg))); + trace_seq_puts(seq, str); + break; + } + case 'o': case 'u': case 'x': case 'X': { + ktap_number n = nvalue(kp_arg(ks, arg)); + unsigned INTFRM_T ni = (unsigned INTFRM_T)n; + addlenmod(form, INTFRMLEN); + trace_seq_printf(seq, form, ni); + break; + } + case 's': { + ktap_value *v = kp_arg(ks, arg); + const char *s; + size_t l; + + if (isnil(v)) { + trace_seq_puts(seq, "nil"); + return 0; + } + + if (ttisevent(v)) { + kp_event_tostring(ks, seq); + return 0; + } + + s = svalue(v); + l = rawtsvalue(v)->tsv.len; + if (!strchr(form, '.') && l >= 100) { + /* + * no precision and string is too long + * to be formatted; + * keep original string + */ + trace_seq_puts(seq, s); + break; + } else { + trace_seq_printf(seq, form, s); + break; + } + } + default: /* also treat cases `pnLlh' */ + kp_error(ks, "invalid option " KTAP_QL("%%%c") + " to " KTAP_QL("format"), + *(strfrmt - 1)); + } + } + } + + return 0; +} + diff --git a/drivers/staging/ktap/interpreter/table.c b/drivers/staging/ktap/interpreter/table.c new file mode 100644 index 000000000000..4e83947ba02d --- /dev/null +++ b/drivers/staging/ktap/interpreter/table.c @@ -0,0 +1,1292 @@ +/* + * table.c - ktap table data structure manipulation function + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * Copyright (C) 1994-2013 Lua.org, PUC-Rio. + * - The part of code in this file is copied from lua initially. + * - lua's MIT license is compatible with GPL. + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef __KERNEL__ +#include "../include/ktap.h" +#include +#include +#include +#else +#include "../include/ktap_types.h" + +static inline void sort(void *base, size_t num, size_t size, + int (*cmp_func)(const void *, const void *), + void (*swap_func)(void *, void *, int size)) +{} +#endif + + +#ifdef __KERNEL__ +#define kp_table_lock_init(t) \ + do { \ + t->lock = (arch_spinlock_t)__ARCH_SPIN_LOCK_UNLOCKED; \ + } while (0) +#define kp_table_lock(t) \ + do { \ + local_irq_save(flags); \ + arch_spin_lock(&t->lock); \ + } while (0) +#define kp_table_unlock(t) \ + do { \ + arch_spin_unlock(&t->lock); \ + local_irq_restore(flags); \ + } while (0) + +#else +#define kp_table_lock_init(t) +#define kp_table_lock(t) +#define kp_table_unlock(t) +#endif + +#define MAXBITS 30 +#define MAXASIZE (1 << MAXBITS) + + +#define NILCONSTANT {NULL}, KTAP_TNIL +const struct ktap_value ktap_nilobjectv = {NILCONSTANT}; +#define ktap_nilobject (&ktap_nilobjectv) + +static const ktap_tnode dummynode_ = { + {NILCONSTANT}, /* value */ + {{NILCONSTANT, NULL}}, /* key */ +}; + +#define gnode(t,i) (&(t)->node[i]) +#define gkey(n) (&(n)->i_key.tvk) +#define gval(n) (&(n)->i_val) +#define gnext(n) ((n)->i_key.nk.next) + +#define twoto(x) (1<<(x)) +#define sizenode(t) (twoto((t)->lsizenode)) + +#define hashpow2(t,n) (gnode(t, lmod((n), sizenode(t)))) + +#define hashmod(t,n) (gnode(t, ((n) % ((sizenode(t)-1)|1)))) + +#define hashstr(t,str) hashpow2(t, (str)->tsv.hash) +#define hashboolean(t,p) hashpow2(t, p) +#define hashnum(t, n) hashmod(t, (unsigned int)n) +#define hashpointer(t,p) hashmod(t, (unsigned long)(p)) + +#define dummynode (&dummynode_) +#define isdummy(n) ((n) == dummynode) + +static void table_setint(ktap_state *ks, ktap_table *t, int key, ktap_value *v); +static ktap_value *table_set(ktap_state *ks, ktap_table *t, + const ktap_value *key); +static void setnodevector(ktap_state *ks, ktap_table *t, int size); + +static int ceillog2(unsigned int x) +{ + static const u8 log_2[256] = { + 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 + }; + + int l = 0; + + x--; + while (x >= 256) { l += 8; x >>= 8; } + return l + log_2[x]; +} + + +ktap_table *kp_table_new(ktap_state *ks) +{ + ktap_table *t = &kp_newobject(ks, KTAP_TTABLE, sizeof(ktap_table), + NULL)->h; + t->flags = (u8)(~0); + t->array = NULL; + t->sizearray = 0; + t->node = (ktap_tnode *)dummynode; + t->gclist = NULL; + setnodevector(ks, t, 0); + + kp_table_lock_init(t); + return t; +} + +static const ktap_value *table_getint(ktap_table *t, int key) +{ + ktap_tnode *n; + + if ((unsigned int)(key - 1) < (unsigned int)t->sizearray) + return &t->array[key - 1]; + + n = hashnum(t, key); + do { + if (ttisnumber(gkey(n)) && nvalue(gkey(n)) == key) + return gval(n); + else + n = gnext(n); + } while (n); + + return ktap_nilobject; +} + +const ktap_value *kp_table_getint(ktap_table *t, int key) +{ + const ktap_value *val; + unsigned long __maybe_unused flags; + + kp_table_lock(t); + val = table_getint(t, key); + kp_table_unlock(t); + + return val; +} + +static ktap_tnode *mainposition (const ktap_table *t, const ktap_value *key) +{ + switch (ttype(key)) { + case KTAP_TNUMBER: + return hashnum(t, nvalue(key)); + case KTAP_TLNGSTR: { + ktap_string *s = rawtsvalue(key); + if (s->tsv.extra == 0) { /* no hash? */ + s->tsv.hash = kp_string_hash(getstr(s), s->tsv.len, + s->tsv.hash); + s->tsv.extra = 1; /* now it has its hash */ + } + return hashstr(t, rawtsvalue(key)); + } + case KTAP_TSHRSTR: + return hashstr(t, rawtsvalue(key)); + case KTAP_TBOOLEAN: + return hashboolean(t, bvalue(key)); + case KTAP_TLIGHTUSERDATA: + return hashpointer(t, pvalue(key)); + case KTAP_TLCF: + return hashpointer(t, fvalue(key)); + case KTAP_TBTRACE: + /* use first entry as hash key, cannot use gcvalue as key */ + return hashpointer(t, btvalue(key)->entries[0]); + default: + return hashpointer(t, gcvalue(key)); + } +} + +static int arrayindex(const ktap_value *key) +{ + if (ttisnumber(key)) { + ktap_number n = nvalue(key); + int k = (int)n; + if ((ktap_number)k == n) + return k; + } + + /* `key' did not match some condition */ + return -1; +} + +/* + * returns the index of a `key' for table traversals. First goes all + * elements in the array part, then elements in the hash part. The + * beginning of a traversal is signaled by -1. + */ +static int findindex(ktap_state *ks, ktap_table *t, StkId key) +{ + int i; + + if (ttisnil(key)) + return -1; /* first iteration */ + + i = arrayindex(key); + if (i > 0 && i <= t->sizearray) /* is `key' inside array part? */ + return i - 1; /* yes; that's the index (corrected to C) */ + else { + ktap_tnode *n = mainposition(t, key); + for (;;) { /* check whether `key' is somewhere in the chain */ + /* key may be dead already, but it is ok to use it in `next' */ + if (kp_equalobjv(ks, gkey(n), key)) { + i = n - gnode(t, 0); /* key index in hash table */ + /* hash elements are numbered after array ones */ + return i + t->sizearray; + } else + n = gnext(n); + + if (n == NULL) + /* key not found */ + kp_error(ks, "invalid table key to next"); + } + } +} + +int kp_table_next(ktap_state *ks, ktap_table *t, StkId key) +{ + unsigned long __maybe_unused flags; + int i; + + kp_table_lock(t); + + i = findindex(ks, t, key); /* find original element */ + + for (i++; i < t->sizearray; i++) { /* try first array part */ + if (!ttisnil(&t->array[i])) { /* a non-nil value? */ + setnvalue(key, i+1); + setobj(key+1, &t->array[i]); + kp_table_unlock(t); + return 1; + } + } + + for (i -= t->sizearray; i < sizenode(t); i++) { /* then hash part */ + if (!ttisnil(gval(gnode(t, i)))) { /* a non-nil value? */ + setobj(key, gkey(gnode(t, i))); + setobj(key+1, gval(gnode(t, i))); + kp_table_unlock(t); + return 1; + } + } + + kp_table_unlock(t); + return 0; /* no more elements */ +} + + + +static int computesizes (int nums[], int *narray) +{ + int i; + int twotoi; /* 2^i */ + int a = 0; /* number of elements smaller than 2^i */ + int na = 0; /* number of elements to go to array part */ + int n = 0; /* optimal size for array part */ + + for (i = 0, twotoi = 1; twotoi/2 < *narray; i++, twotoi *= 2) { + if (nums[i] > 0) { + a += nums[i]; + /* more than half elements present? */ + if (a > twotoi/2) { + /* optimal size (till now) */ + n = twotoi; + /* + * all elements smaller than n will go to + * array part + */ + na = a; + } + } + if (a == *narray) + break; /* all elements already counted */ + } + *narray = n; + return na; +} + + +static int countint(const ktap_value *key, int *nums) +{ + int k = arrayindex(key); + + /* is `key' an appropriate array index? */ + if (0 < k && k <= MAXASIZE) { + nums[ceillog2(k)]++; /* count as such */ + return 1; + } else + return 0; +} + + +static int numusearray(const ktap_table *t, int *nums) +{ + int lg; + int ttlg; /* 2^lg */ + int ause = 0; /* summation of `nums' */ + int i = 1; /* count to traverse all array keys */ + + /* for each slice */ + for (lg=0, ttlg=1; lg <= MAXBITS; lg++, ttlg *= 2) { + int lc = 0; /* counter */ + int lim = ttlg; + + if (lim > t->sizearray) { + lim = t->sizearray; /* adjust upper limit */ + if (i > lim) + break; /* no more elements to count */ + } + + /* count elements in range (2^(lg-1), 2^lg] */ + for (; i <= lim; i++) { + if (!ttisnil(&t->array[i-1])) + lc++; + } + nums[lg] += lc; + ause += lc; + } + return ause; +} + +static int numusehash(const ktap_table *t, int *nums, int *pnasize) +{ + int totaluse = 0; /* total number of elements */ + int ause = 0; /* summation of `nums' */ + int i = sizenode(t); + + while (i--) { + ktap_tnode *n = &t->node[i]; + if (!isnil(gval(n))) { + ause += countint(gkey(n), nums); + totaluse++; + } + } + + *pnasize += ause; + return totaluse; +} + + +static void setarrayvector(ktap_state *ks, ktap_table *t, int size) +{ + int i; + + kp_realloc(ks, t->array, t->sizearray, size, ktap_value); + for (i = t->sizearray; i < size; i++) + setnilvalue(&t->array[i]); + + t->sizearray = size; +} + +static void setnodevector(ktap_state *ks, ktap_table *t, int size) +{ + int lsize; + + if (size == 0) { /* no elements to hash part? */ + t->node = (ktap_tnode *)dummynode; /* use common `dummynode' */ + lsize = 0; + } else { + int i; + lsize = ceillog2(size); + if (lsize > MAXBITS) { + kp_error(ks, "table overflow\n"); + return; + } + + size = twoto(lsize); + t->node = kp_malloc(ks, size * sizeof(ktap_tnode)); + for (i = 0; i < size; i++) { + ktap_tnode *n = gnode(t, i); + gnext(n) = NULL; + setnilvalue(gkey(n)); + setnilvalue(gval(n)); + } + } + + t->lsizenode = (u8)lsize; + t->lastfree = gnode(t, size); /* all positions are free */ +} + +static void table_resize(ktap_state *ks, ktap_table *t, int nasize, int nhsize) +{ + int i; + int oldasize = t->sizearray; + int oldhsize = t->lsizenode; + ktap_tnode *nold = t->node; /* save old hash ... */ + +#ifdef __KERNEL__ + kp_verbose_printf(ks, "table resize, nasize: %d, nhsize: %d\n", + nasize, nhsize); +#endif + + if (nasize > oldasize) /* array part must grow? */ + setarrayvector(ks, t, nasize); + + /* create new hash part with appropriate size */ + setnodevector(ks, t, nhsize); + + if (nasize < oldasize) { /* array part must shrink? */ + t->sizearray = nasize; + /* re-insert elements from vanishing slice */ + for (i=nasize; iarray[i])) + table_setint(ks, t, i + 1, &t->array[i]); + } + + /* shrink array */ + kp_realloc(ks, t->array, oldasize, nasize, ktap_value); + } + + /* re-insert elements from hash part */ + for (i = twoto(oldhsize) - 1; i >= 0; i--) { + ktap_tnode *old = nold+i; + if (!ttisnil(gval(old))) { + /* + * doesn't need barrier/invalidate cache, as entry was + * already present in the table + */ + setobj(table_set(ks, t, gkey(old)), gval(old)); + } + } + + if (!isdummy(nold)) + kp_free(ks, nold); /* free old array */ +} + +void kp_table_resize(ktap_state *ks, ktap_table *t, int nasize, int nhsize) +{ + unsigned long __maybe_unused flags; + + kp_table_lock(t); + table_resize(ks, t, nasize, nhsize); + kp_table_unlock(t); +} + +void kp_table_resizearray(ktap_state *ks, ktap_table *t, int nasize) +{ + unsigned long __maybe_unused flags; + int nsize; + + kp_table_lock(t); + + nsize = isdummy(t->node) ? 0 : sizenode(t); + table_resize(ks, t, nasize, nsize); + + kp_table_unlock(t); +} + +static void rehash(ktap_state *ks, ktap_table *t, const ktap_value *ek) +{ + int nasize, na; + /* nums[i] = number of keys with 2^(i-1) < k <= 2^i */ + int nums[MAXBITS+1]; + int i; + int totaluse; + + for (i = 0; i <= MAXBITS; i++) + nums[i] = 0; /* reset counts */ + + nasize = numusearray(t, nums); /* count keys in array part */ + totaluse = nasize; /* all those keys are integer keys */ + totaluse += numusehash(t, nums, &nasize); /* count keys in hash part */ + /* count extra key */ + nasize += countint(ek, nums); + totaluse++; + /* compute new size for array part */ + na = computesizes(nums, &nasize); + /* resize the table to new computed sizes */ + table_resize(ks, t, nasize, totaluse - na); +} + + +static ktap_tnode *getfreepos(ktap_table *t) +{ + while (t->lastfree > t->node) { + t->lastfree--; + if (isnil(gkey(t->lastfree))) + return t->lastfree; + } + return NULL; /* could not find a free place */ +} + + +static ktap_value *table_newkey(ktap_state *ks, ktap_table *t, + const ktap_value *key) +{ + ktap_tnode *mp; + ktap_value newkey; + + mp = mainposition(t, key); + if (!isnil(gval(mp)) || isdummy(mp)) { /* main position is taken? */ + ktap_tnode *othern; + ktap_tnode *n = getfreepos(t); /* get a free place */ + if (n == NULL) { /* cannot find a free place? */ + rehash(ks, t, key); /* grow table */ + /* whatever called 'newkey' take care of TM cache and GC barrier */ + return table_set(ks, t, key); /* insert key into grown table */ + } + + othern = mainposition(t, gkey(mp)); + if (othern != mp) { /* is colliding node out of its main position? */ + /* yes; move colliding node into free position */ + while (gnext(othern) != mp) + othern = gnext(othern); /* find previous */ + gnext(othern) = n; /* redo the chain with `n' in place of `mp' */ + *n = *mp; /* copy colliding node into free pos. (mp->next also goes) */ + gnext(mp) = NULL; /* now `mp' is free */ + setnilvalue(gval(mp)); + } else { /* colliding node is in its own main position */ + /* new node will go into free position */ + gnext(n) = gnext(mp); /* chain new position */ + gnext(mp) = n; + mp = n; + } + } + + /* special handling for cloneable object, maily for btrace object */ + if (ttisclone(key)) + kp_objclone(ks, key, &newkey, &t->gclist); + else + newkey = *key; + + setobj(gkey(mp), &newkey); + return gval(mp); +} + + +/* + * search function for short strings + */ +static const ktap_value *table_getstr(ktap_table *t, ktap_string *key) +{ + ktap_tnode *n = hashstr(t, key); + + do { /* check whether `key' is somewhere in the chain */ + if (ttisshrstring(gkey(n)) && eqshrstr(rawtsvalue(gkey(n)), + key)) + return gval(n); /* that's it */ + else + n = gnext(n); + } while (n); + + return ktap_nilobject; +} + + +/* + * main search function + */ +static const ktap_value *table_get(ktap_table *t, const ktap_value *key) +{ + switch (ttype(key)) { + case KTAP_TNIL: + return ktap_nilobject; + case KTAP_TSHRSTR: + return table_getstr(t, rawtsvalue(key)); + case KTAP_TNUMBER: { + ktap_number n = nvalue(key); + int k = (int)n; + if ((ktap_number)k == nvalue(key)) /* index is int? */ + return table_getint(t, k); /* use specialized version */ + /* else go through */ + } + default: { + ktap_tnode *n = mainposition(t, key); + do { /* check whether `key' is somewhere in the chain */ + if (rawequalobj(gkey(n), key)) + return gval(n); /* that's it */ + else + n = gnext(n); + } while (n); + + return ktap_nilobject; + } + } +} + +const ktap_value *kp_table_get(ktap_table *t, const ktap_value *key) +{ + const ktap_value *val; + unsigned long __maybe_unused flags; + + kp_table_lock(t); + val = table_get(t, key); + kp_table_unlock(t); + + return val; +} + +static ktap_value *table_set(ktap_state *ks, ktap_table *t, + const ktap_value *key) +{ + const ktap_value *p = table_get(t, key); + + if (p != ktap_nilobject) + return (ktap_value *)p; + else + return table_newkey(ks, t, key); +} + +void kp_table_setvalue(ktap_state *ks, ktap_table *t, + const ktap_value *key, ktap_value *val) +{ + unsigned long __maybe_unused flags; + + if (isnil(key)) { + kp_printf(ks, "table index is nil\n"); + kp_exit(ks); + return; + } + + kp_table_lock(t); + setobj(table_set(ks, t, key), val); + kp_table_unlock(t); +} + +static void table_setint(ktap_state *ks, ktap_table *t, int key, ktap_value *v) +{ + const ktap_value *p; + ktap_value *cell; + + p = table_getint(t, key); + + if (p != ktap_nilobject) + cell = (ktap_value *)p; + else { + ktap_value k; + setnvalue(&k, key); + cell = table_newkey(ks, t, &k); + } + + setobj(cell, v); +} + +void kp_table_setint(ktap_state *ks, ktap_table *t, int key, ktap_value *val) +{ + unsigned long __maybe_unused flags; + + kp_table_lock(t); + table_setint(ks, t, key, val); + kp_table_unlock(t); +} + +void kp_table_atomic_inc(ktap_state *ks, ktap_table *t, ktap_value *key, int n) +{ + unsigned long __maybe_unused flags; + ktap_value *v; + + if (isnil(key)) { + kp_printf(ks, "table index is nil\n"); + kp_exit(ks); + return; + } + + kp_table_lock(t); + + v = table_set(ks, t, key); + if (isnil(v)) { + setnvalue(v, n); + } else + setnvalue(v, nvalue(v) + n); + + kp_table_unlock(t); +} + +int kp_table_length(ktap_state *ks, ktap_table *t) +{ + unsigned long __maybe_unused flags; + int i, len = 0; + + kp_table_lock(t); + + for (i = 0; i < t->sizearray; i++) { + ktap_value *v = &t->array[i]; + + if (isnil(v)) + continue; + len++; + } + + for (i = 0; i < sizenode(t); i++) { + ktap_tnode *n = &t->node[i]; + + if (isnil(gkey(n))) + continue; + + len++; + } + + kp_table_unlock(t); + return len; +} + +void kp_table_free(ktap_state *ks, ktap_table *t) +{ + if (t->sizearray > 0) + kp_free(ks, t->array); + if (!isdummy(t->node)) + kp_free(ks, t->node); + + kp_free_gclist(ks, t->gclist); + kp_free(ks, t); +} + +void kp_table_dump(ktap_state *ks, ktap_table *t) +{ + int i, count = 0; + + kp_puts(ks, "{"); + for (i = 0; i < t->sizearray; i++) { + ktap_value *v = &t->array[i]; + + if (isnil(v)) + continue; + + if (count) + kp_puts(ks, ", "); + + kp_printf(ks, "(%d: ", i + 1); + kp_showobj(ks, v); + kp_puts(ks, ")"); + count++; + } + + for (i = 0; i < sizenode(t); i++) { + ktap_tnode *n = &t->node[i]; + + if (isnil(gkey(n))) + continue; + + if (count) + kp_puts(ks, ", "); + + kp_puts(ks, "("); + kp_showobj(ks, gkey(n)); + kp_puts(ks, ": "); + kp_showobj(ks, gval(n)); + kp_puts(ks, ")"); + count++; + } + kp_puts(ks, "}"); +} + +/* + * table-clear only set nil of all elements, not free t->array and nodes. + * we assume user will reuse table soon after clear table, so reserve array + * and nodes will avoid memory allocation when insert key-value again. + */ +void kp_table_clear(ktap_state *ks, ktap_table *t) +{ + unsigned long __maybe_unused flags; + int i; + + kp_table_lock(t); + + for (i = 0; i < t->sizearray; i++) { + ktap_value *v = &t->array[i]; + + if (isnil(v)) + continue; + + setnilvalue(v); + } + + for (i = 0; i < sizenode(t); i++) { + ktap_tnode *n = &t->node[i]; + + if (isnil(gkey(n))) + continue; + + setnilvalue(gkey(n)); + setnilvalue(gval(n)); + } + + kp_table_unlock(t); +} + +#ifdef __KERNEL__ +static void string_convert(char *output, const char *input) +{ + if (strlen(input) > 32) { + strncpy(output, input, 32-4); + memset(output + 32-4, '.', 3); + } else + memcpy(output, input, strlen(input)); +} + +struct table_hist_record { + ktap_value key; + ktap_value val; +}; + +static int hist_record_cmp(const void *r1, const void *r2) +{ + const struct table_hist_record *i = r1; + const struct table_hist_record *j = r2; + + if ((nvalue(&i->val) == nvalue(&j->val))) { + return 0; + } else if ((nvalue(&i->val) < nvalue(&j->val))) { + return 1; + } else + return -1; +} + +static int kp_aggracc_read(ktap_aggraccval *acc); + +/* histogram: key should be number or string, value must be number */ +static void table_histdump(ktap_state *ks, ktap_table *t, int shownums) +{ + struct table_hist_record *thr; + unsigned long __maybe_unused flags; + char dist_str[40]; + int i, ratio, total = 0, count = 0, top_num, is_kernel_address = 0; + int size, num; + + size = sizeof(*thr) * (t->sizearray + sizenode(t)); + thr = kp_malloc(ks, size); + if (!thr) { + kp_error(ks, "Cannot allocate %d of histogram memory", size); + return; + } + + kp_table_lock(t); + + for (i = 0; i < t->sizearray; i++) { + ktap_value *v = &t->array[i]; + + if (isnil(v)) + continue; + + if (ttisnumber(v)) + num = nvalue(v); + else if (ttisaggracc(v)) + num = kp_aggracc_read(aggraccvalue(v)); + else { + kp_table_unlock(t); + goto error; + } + + setnvalue(&thr[count].key, i + 1); + setnvalue(&thr[count].val, num); + count++; + total += num; + } + + for (i = 0; i < sizenode(t); i++) { + ktap_tnode *n = &t->node[i]; + ktap_value *v = gval(n); + + if (isnil(gkey(n))) + continue; + + if (ttisnumber(v)) + num = nvalue(v); + else if (ttisaggracc(v)) + num = kp_aggracc_read(aggraccvalue(v)); + else { + kp_table_unlock(t); + goto error; + } + + setobj(&thr[count].key, gkey(n)); + setnvalue(&thr[count].val, num); + count++; + total += num; + } + + kp_table_unlock(t); + + sort(thr, count, sizeof(struct table_hist_record), + hist_record_cmp, NULL); + + dist_str[sizeof(dist_str) - 1] = '\0'; + + /* check the first key is a kernel text symbol or not */ + if (ttisnumber(&thr[0].key)) { + char str[KSYM_SYMBOL_LEN]; + + SPRINT_SYMBOL(str, nvalue(&thr[0].key)); + if (str[0] != '0' || str[1] != 'x') + is_kernel_address = 1; + } + + top_num = min(shownums, count); + for (i = 0; i < top_num; i++) { + ktap_value *key = &thr[i].key; + ktap_value *val = &thr[i].val; + + memset(dist_str, ' ', sizeof(dist_str) - 1); + ratio = (nvalue(val) * (sizeof(dist_str) - 1)) / total; + memset(dist_str, '@', ratio); + + if (ttisstring(key)) { + char buf[32 + 1] = {0}; + + string_convert(buf, svalue(key)); + kp_printf(ks, "%32s |%s%-7d\n", buf, dist_str, + nvalue(val)); + } else if (ttisnumber(key)) { + char str[KSYM_SYMBOL_LEN]; + char buf[32 + 1] = {0}; + + if (is_kernel_address) { + /* suppose it's a symbol, fix it in future */ + SPRINT_SYMBOL(str, nvalue(key)); + string_convert(buf, str); + kp_printf(ks, "%32s |%s%-7d\n", buf, dist_str, + nvalue(val)); + } else { + kp_printf(ks, "%32d |%s%-7d\n", nvalue(key), + dist_str, nvalue(val)); + } + } + } + + if (count > shownums) + kp_printf(ks, "%32s |\n", "..."); + + goto out; + + error: + kp_puts(ks, "error: table histogram only handle " + " (key: string/number val: number)\n"); + out: + kp_free(ks, thr); +} + +#define HISTOGRAM_DEFAULT_TOP_NUM 20 + +#define DISTRIBUTION_STR "------------- Distribution -------------" +void kp_table_histogram(ktap_state *ks, ktap_table *t) +{ + kp_printf(ks, "%32s%s%s\n", "value ", DISTRIBUTION_STR, " count"); + table_histdump(ks, t, HISTOGRAM_DEFAULT_TOP_NUM); +} + +/* + * Aggregation Table + */ + +static ktap_table *table_new2(ktap_state *ks, ktap_gcobject **list) +{ + ktap_table *t = &kp_newobject(ks, KTAP_TTABLE, sizeof(ktap_table), + list)->h; + t->flags = (u8)(~0); + t->array = NULL; + t->sizearray = 0; + t->node = (ktap_tnode *)dummynode; + t->gclist = NULL; + setnodevector(ks, t, 0); + + kp_table_lock_init(t); + return t; +} + +static int kp_aggracc_read(ktap_aggraccval *acc) +{ + switch (acc->type) { + case AGGREGATION_TYPE_COUNT: + case AGGREGATION_TYPE_MAX: + case AGGREGATION_TYPE_MIN: + case AGGREGATION_TYPE_SUM: + return acc->val; + case AGGREGATION_TYPE_AVG: + return acc->val / acc->more; + default: + return 0; + } + +} + +void kp_aggraccval_dump(ktap_state *ks, ktap_aggraccval *acc) +{ + switch (acc->type) { + case AGGREGATION_TYPE_COUNT: + case AGGREGATION_TYPE_MAX: + case AGGREGATION_TYPE_MIN: + case AGGREGATION_TYPE_SUM: + kp_printf(ks, "%d", acc->val); + break; + case AGGREGATION_TYPE_AVG: + kp_printf(ks, "%d", acc->val / acc->more); + break; + default: + break; + } +} + +static void synth_acc(ktap_aggraccval *acc1, ktap_aggraccval *acc2) +{ + switch (acc1->type) { + case AGGREGATION_TYPE_COUNT: + acc2->val += acc1->val; + break; + case AGGREGATION_TYPE_MAX: + acc2->val = max(acc1->val, acc2->val); + break; + case AGGREGATION_TYPE_MIN: + acc2->val = min(acc1->val, acc2->val); + break; + case AGGREGATION_TYPE_SUM: + acc2->val += acc1->val; + break; + case AGGREGATION_TYPE_AVG: + acc2->val += acc1->val; + acc2->more += acc1->more; + break; + default: + break; + } +} + +static ktap_aggraccval *get_accval(ktap_state *ks, int type, + ktap_gcobject **list) +{ + ktap_aggraccval *acc; + + acc = &kp_newobject(ks, KTAP_TAGGRACCVAL, sizeof(ktap_aggraccval), + list)->acc; + acc->type = type; + acc->val = 0; + acc->more = 0; + return acc; +} + +static void synth_accval(ktap_state *ks, ktap_value *o1, ktap_value *o2, + ktap_gcobject **list) +{ + ktap_aggraccval *acc; + + if (isnil(o2)) { + acc = get_accval(ks, aggraccvalue(o1)->type, list); + acc->val = aggraccvalue(o1)->val; + acc->more = aggraccvalue(o1)->more; + setaggraccvalue(o2, acc); + return; + } + + synth_acc(aggraccvalue(o1), aggraccvalue(o2)); +} + +static void move_table(ktap_state *ks, ktap_table *t1, ktap_table *t2) +{ + ktap_value *newv; + ktap_value n; + int i; + + for (i = 0; i < t1->sizearray; i++) { + ktap_value *v = &t1->array[i]; + + if (isnil(v)) + continue; + + setnvalue(&n, i); + + newv = table_set(ks, t2, &n); + synth_accval(ks, v, newv, &t2->gclist); + } + + for (i = 0; i < sizenode(t1); i++) { + ktap_tnode *node = &t1->node[i]; + + if (isnil(gkey(node))) + continue; + + newv = table_set(ks, t2, gkey(node)); + synth_accval(ks, gval(node), newv, &t2->gclist); + } +} + +ktap_table *kp_aggrtable_synthesis(ktap_state *ks, ktap_aggrtable *ah) +{ + ktap_table *synth_tbl; + int cpu; + + synth_tbl = table_new2(ks, &ah->gclist); + + for_each_possible_cpu(cpu) { + ktap_table **t = per_cpu_ptr(ah->pcpu_tbl, cpu); + move_table(ks, *t, synth_tbl); + } + + return synth_tbl; +} + +void kp_aggrtable_dump(ktap_state *ks, ktap_aggrtable *ah) +{ + kp_table_dump(ks, kp_aggrtable_synthesis(ks, ah)); +} + +ktap_aggrtable *kp_aggrtable_new(ktap_state *ks) +{ + ktap_aggrtable *ah; + int cpu; + + ah = &kp_newobject(ks, KTAP_TAGGRTABLE, sizeof(ktap_aggrtable), + NULL)->ah; + ah->pcpu_tbl = alloc_percpu(ktap_table *); + ah->gclist = NULL; + + for_each_possible_cpu(cpu) { + ktap_table **t = per_cpu_ptr(ah->pcpu_tbl, cpu); + *t = table_new2(ks, &ah->gclist); + } + + return ah; +} + +void kp_aggrtable_free(ktap_state *ks, ktap_aggrtable *ah) +{ + free_percpu(ah->pcpu_tbl); + kp_free_gclist(ks, ah->gclist); + kp_free(ks, ah); +} + +static +void handle_aggr_count(ktap_state *ks, ktap_aggrtable *ah, ktap_value *key) +{ + ktap_table *t = *__this_cpu_ptr(ah->pcpu_tbl); + ktap_value *v = table_set(ks, t, key); + ktap_aggraccval *acc; + + if (isnil(v)) { + acc = get_accval(ks, AGGREGATION_TYPE_COUNT, &t->gclist); + acc->val = 1; + setaggraccvalue(v, acc); + return; + } + + acc = aggraccvalue(v); + acc->val += 1; +} + +static +void handle_aggr_max(ktap_state *ks, ktap_aggrtable *ah, ktap_value *key) +{ + ktap_table *t = *__this_cpu_ptr(ah->pcpu_tbl); + ktap_value *v = table_set(ks, t, key); + ktap_aggraccval *acc; + + if (isnil(v)) { + acc = get_accval(ks, AGGREGATION_TYPE_MAX, &t->gclist); + acc->val = ks->aggr_accval; + setaggraccvalue(v, acc); + return; + } + + acc = aggraccvalue(v); + acc->val = max(acc->val, ks->aggr_accval); +} + +static +void handle_aggr_min(ktap_state *ks, ktap_aggrtable *ah, ktap_value *key) +{ + ktap_table *t = *__this_cpu_ptr(ah->pcpu_tbl); + ktap_value *v = table_set(ks, t, key); + ktap_aggraccval *acc; + + if (isnil(v)) { + acc = get_accval(ks, AGGREGATION_TYPE_MIN, &t->gclist); + acc->val = ks->aggr_accval; + setaggraccvalue(v, acc); + return; + } + + acc = aggraccvalue(v); + acc->val = min(acc->val, ks->aggr_accval); +} + +static +void handle_aggr_sum(ktap_state *ks, ktap_aggrtable *ah, ktap_value *key) +{ + ktap_table *t = *__this_cpu_ptr(ah->pcpu_tbl); + ktap_value *v = table_set(ks, t, key); + ktap_aggraccval *acc; + + if (isnil(v)) { + acc = get_accval(ks, AGGREGATION_TYPE_SUM, &t->gclist); + acc->val = ks->aggr_accval; + setaggraccvalue(v, acc); + return; + } + + acc = aggraccvalue(v); + acc->val += ks->aggr_accval; +} + +static +void handle_aggr_avg(ktap_state *ks, ktap_aggrtable *ah, ktap_value *key) +{ + ktap_table *t = *__this_cpu_ptr(ah->pcpu_tbl); + ktap_value *v = table_set(ks, t, key); + ktap_aggraccval *acc; + + if (isnil(v)) { + acc = get_accval(ks, AGGREGATION_TYPE_AVG, &t->gclist); + acc->val = ks->aggr_accval; + acc->more = 1; + setaggraccvalue(v, acc); + return; + } + + acc = aggraccvalue(v); + acc->val += ks->aggr_accval; + acc->more++; +} + +typedef void (*aggr_func_t)(ktap_state *ks, ktap_aggrtable *ah, ktap_value *k); +static aggr_func_t kp_aggregation_handler[] = { + handle_aggr_count, + handle_aggr_max, + handle_aggr_min, + handle_aggr_sum, + handle_aggr_avg +}; + +void kp_aggrtable_set(ktap_state *ks, ktap_aggrtable *ah, + ktap_value *key, ktap_value *val) +{ + if (unlikely(!ttisaggrval(val))) { + kp_error(ks, "set invalid value to aggregation table\n"); + return; + } + + kp_aggregation_handler[nvalue(val)](ks, ah, key); +} + + +void kp_aggrtable_get(ktap_state *ks, ktap_aggrtable *ah, ktap_value *key, + ktap_value *val) +{ + ktap_aggraccval acc; /* in stack */ + const ktap_value *v; + int cpu; + + acc.val = -1; + acc.more = -1; + + for_each_possible_cpu(cpu) { + ktap_table **t = per_cpu_ptr(ah->pcpu_tbl, cpu); + + v = table_get(*t, key); + if (isnil(v)) + continue; + + if (acc.more == -1) { + acc = *aggraccvalue(v); + continue; + } + + synth_acc(aggraccvalue(v), &acc); + } + + if (acc.more == -1) { + setnilvalue(val); + } else { + setnvalue(val, kp_aggracc_read(&acc)); + } +} + +void kp_aggrtable_histogram(ktap_state *ks, ktap_aggrtable *ah) +{ + kp_table_histogram(ks, kp_aggrtable_synthesis(ks, ah)); +} +#endif diff --git a/drivers/staging/ktap/interpreter/transport.c b/drivers/staging/ktap/interpreter/transport.c new file mode 100644 index 000000000000..4cd3662b792a --- /dev/null +++ b/drivers/staging/ktap/interpreter/transport.c @@ -0,0 +1,636 @@ +/* + * transport.c - ktap transport functionality + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include "../include/ktap.h" + +struct ktap_trace_iterator { + struct ring_buffer *buffer; + int print_timestamp; + void *private; + + struct trace_iterator iter; +}; + +enum ktap_trace_type { + __TRACE_FIRST_TYPE = 0, + + TRACE_FN = 1, /* must be same as ftrace definition in kernel */ + TRACE_PRINT, + TRACE_BPUTS, + TRACE_STACK, + TRACE_USER_STACK, + + __TRACE_LAST_TYPE, +}; + +#define KTAP_TRACE_ITER(iter) \ + container_of(iter, struct ktap_trace_iterator, iter) + +ssize_t trace_seq_to_user(struct trace_seq *s, char __user *ubuf, size_t cnt) +{ + int len; + int ret; + + if (!cnt) + return 0; + + if (s->len <= s->readpos) + return -EBUSY; + + len = s->len - s->readpos; + if (cnt > len) + cnt = len; + ret = copy_to_user(ubuf, s->buffer + s->readpos, cnt); + if (ret == cnt) + return -EFAULT; + + cnt -= ret; + + s->readpos += cnt; + return cnt; +} + +int trace_seq_puts(struct trace_seq *s, const char *str) +{ + int len = strlen(str); + + if (s->full) + return 0; + + if (len > ((PAGE_SIZE - 1) - s->len)) { + s->full = 1; + return 0; + } + + memcpy(s->buffer + s->len, str, len); + s->len += len; + + return len; +} + +static int trace_empty(struct trace_iterator *iter) +{ + struct ktap_trace_iterator *ktap_iter = KTAP_TRACE_ITER(iter); + int cpu; + + for_each_online_cpu(cpu) { + if (!ring_buffer_empty_cpu(ktap_iter->buffer, cpu)) + return 0; + } + + return 1; +} + +static void trace_consume(struct trace_iterator *iter) +{ + struct ktap_trace_iterator *ktap_iter = KTAP_TRACE_ITER(iter); + + ring_buffer_consume(ktap_iter->buffer, iter->cpu, &iter->ts, + &iter->lost_events); +} + +unsigned long long ns2usecs(cycle_t nsec) +{ + nsec += 500; + do_div(nsec, 1000); + return nsec; +} + +static int trace_print_timestamp(struct trace_iterator *iter) +{ + struct trace_seq *s = &iter->seq; + unsigned long long t; + unsigned long secs, usec_rem; + + t = ns2usecs(iter->ts); + usec_rem = do_div(t, USEC_PER_SEC); + secs = (unsigned long)t; + + return trace_seq_printf(s, "%5lu.%06lu: ", secs, usec_rem); +} + +/* todo: export kernel function ftrace_find_event in future, and make faster */ +static struct trace_event *(*ftrace_find_event)(int type); + +static enum print_line_t print_trace_fmt(struct trace_iterator *iter) +{ + struct ktap_trace_iterator *ktap_iter = KTAP_TRACE_ITER(iter); + struct trace_entry *entry = iter->ent; + struct trace_event *ev; + + ev = ftrace_find_event(entry->type); + + if (ktap_iter->print_timestamp && !trace_print_timestamp(iter)) + return TRACE_TYPE_PARTIAL_LINE; + + if (ev) { + int ret = ev->funcs->trace(iter, 0, ev); + + /* overwrite '\n' at the ending */ + iter->seq.buffer[iter->seq.len - 1] = '\0'; + iter->seq.len--; + return ret; + } + + return TRACE_TYPE_PARTIAL_LINE; +} + +static enum print_line_t print_trace_stack(struct trace_iterator *iter) +{ + struct trace_entry *entry = iter->ent; + struct stack_trace trace; + char str[KSYM_SYMBOL_LEN]; + int i; + + trace.entries = (unsigned long *)(entry + 1); + trace.nr_entries = (iter->ent_size - sizeof(*entry)) / + sizeof(unsigned long); + + if (!trace_seq_puts(&iter->seq, "\n")) + return TRACE_TYPE_PARTIAL_LINE; + + for (i = 0; i < trace.nr_entries; i++) { + unsigned long p = trace.entries[i]; + + if (p == ULONG_MAX) + break; + + sprint_symbol(str, p); + if (!trace_seq_printf(&iter->seq, " => %s\n", str)) + return TRACE_TYPE_PARTIAL_LINE; + } + + return TRACE_TYPE_HANDLED; +} + +struct ktap_ftrace_entry { + struct trace_entry entry; + unsigned long ip; + unsigned long parent_ip; +}; + +static enum print_line_t print_trace_fn(struct trace_iterator *iter) +{ + struct ktap_trace_iterator *ktap_iter = KTAP_TRACE_ITER(iter); + struct ktap_ftrace_entry *field = (struct ktap_ftrace_entry *)iter->ent; + char str[KSYM_SYMBOL_LEN]; + + if (ktap_iter->print_timestamp && !trace_print_timestamp(iter)) + return TRACE_TYPE_PARTIAL_LINE; + + sprint_symbol(str, field->ip); + if (!trace_seq_puts(&iter->seq, str)) + return TRACE_TYPE_PARTIAL_LINE; + + if (!trace_seq_puts(&iter->seq, " <- ")) + return TRACE_TYPE_PARTIAL_LINE; + + sprint_symbol(str, field->parent_ip); + if (!trace_seq_puts(&iter->seq, str)) + return TRACE_TYPE_PARTIAL_LINE; + + return TRACE_TYPE_HANDLED; +} + +static enum print_line_t print_trace_bputs(struct trace_iterator *iter) +{ + if (!trace_seq_puts(&iter->seq, + (const char *)(*(unsigned long *)(iter->ent + 1)))) + return TRACE_TYPE_PARTIAL_LINE; + + return TRACE_TYPE_HANDLED; +} + +static enum print_line_t print_trace_line(struct trace_iterator *iter) +{ + struct trace_entry *entry = iter->ent; + char *str = (char *)(entry + 1); + + if (entry->type == TRACE_PRINT) { + if (!trace_seq_printf(&iter->seq, "%s", str)) + return TRACE_TYPE_PARTIAL_LINE; + + return TRACE_TYPE_HANDLED; + } + + if (entry->type == TRACE_BPUTS) + return print_trace_bputs(iter); + + if (entry->type == TRACE_STACK) + return print_trace_stack(iter); + + if (entry->type == TRACE_FN) + return print_trace_fn(iter); + + return print_trace_fmt(iter); +} + +static struct trace_entry * +peek_next_entry(struct trace_iterator *iter, int cpu, u64 *ts, + unsigned long *lost_events) +{ + struct ktap_trace_iterator *ktap_iter = KTAP_TRACE_ITER(iter); + struct ring_buffer_event *event; + + event = ring_buffer_peek(ktap_iter->buffer, cpu, ts, lost_events); + if (event) { + iter->ent_size = ring_buffer_event_length(event); + return ring_buffer_event_data(event); + } + + return NULL; +} + +static struct trace_entry * +__find_next_entry(struct trace_iterator *iter, int *ent_cpu, + unsigned long *missing_events, u64 *ent_ts) +{ + struct ktap_trace_iterator *ktap_iter = KTAP_TRACE_ITER(iter); + struct ring_buffer *buffer = ktap_iter->buffer; + struct trace_entry *ent, *next = NULL; + unsigned long lost_events = 0, next_lost = 0; + u64 next_ts = 0, ts; + int next_cpu = -1; + int next_size = 0; + int cpu; + + for_each_online_cpu(cpu) { + if (ring_buffer_empty_cpu(buffer, cpu)) + continue; + + ent = peek_next_entry(iter, cpu, &ts, &lost_events); + /* + * Pick the entry with the smallest timestamp: + */ + if (ent && (!next || ts < next_ts)) { + next = ent; + next_cpu = cpu; + next_ts = ts; + next_lost = lost_events; + next_size = iter->ent_size; + } + } + + iter->ent_size = next_size; + + if (ent_cpu) + *ent_cpu = next_cpu; + + if (ent_ts) + *ent_ts = next_ts; + + if (missing_events) + *missing_events = next_lost; + + return next; +} + +/* Find the next real entry, and increment the iterator to the next entry */ +static void *trace_find_next_entry_inc(struct trace_iterator *iter) +{ + iter->ent = __find_next_entry(iter, &iter->cpu, + &iter->lost_events, &iter->ts); + if (iter->ent) + iter->idx++; + + return iter->ent ? iter : NULL; +} + +static void poll_wait_pipe(void) +{ + set_current_state(TASK_INTERRUPTIBLE); + /* sleep for 100 msecs, and try again. */ + schedule_timeout(HZ / 10); +} + +static int tracing_wait_pipe(struct file *filp) +{ + struct trace_iterator *iter = filp->private_data; + struct ktap_trace_iterator *ktap_iter = KTAP_TRACE_ITER(iter); + ktap_state *ks = ktap_iter->private; + + while (trace_empty(iter)) { + + if ((filp->f_flags & O_NONBLOCK)) { + return -EAGAIN; + } + + mutex_unlock(&iter->mutex); + + poll_wait_pipe(); + + mutex_lock(&iter->mutex); + + if (G(ks)->wait_user && trace_empty(iter)) + return -EINTR; + } + + return 1; +} + +static ssize_t +tracing_read_pipe(struct file *filp, char __user *ubuf, size_t cnt, + loff_t *ppos) +{ + struct trace_iterator *iter = filp->private_data; + ssize_t sret; + + /* return any leftover data */ + sret = trace_seq_to_user(&iter->seq, ubuf, cnt); + if (sret != -EBUSY) + return sret; + /* + * Avoid more than one consumer on a single file descriptor + * This is just a matter of traces coherency, the ring buffer itself + * is protected. + */ + mutex_lock(&iter->mutex); + +waitagain: + sret = tracing_wait_pipe(filp); + if (sret <= 0) + goto out; + + /* stop when tracing is finished */ + if (trace_empty(iter)) { + sret = 0; + goto out; + } + + if (cnt >= PAGE_SIZE) + cnt = PAGE_SIZE - 1; + + /* reset all but tr, trace, and overruns */ + memset(&iter->seq, 0, + sizeof(struct trace_iterator) - + offsetof(struct trace_iterator, seq)); + iter->pos = -1; + + while (trace_find_next_entry_inc(iter) != NULL) { + enum print_line_t ret; + int len = iter->seq.len; + + ret = print_trace_line(iter); + if (ret == TRACE_TYPE_PARTIAL_LINE) { + /* don't print partial lines */ + iter->seq.len = len; + break; + } + if (ret != TRACE_TYPE_NO_CONSUME) + trace_consume(iter); + + if (iter->seq.len >= cnt) + break; + + /* + * Setting the full flag means we reached the trace_seq buffer + * size and we should leave by partial output condition above. + * One of the trace_seq_* functions is not used properly. + */ + WARN_ONCE(iter->seq.full, "full flag set for trace type %d", + iter->ent->type); + } + + /* Now copy what we have to the user */ + sret = trace_seq_to_user(&iter->seq, ubuf, cnt); + if (iter->seq.readpos >= iter->seq.len) + trace_seq_init(&iter->seq); + + /* + * If there was nothing to send to user, in spite of consuming trace + * entries, go back to wait for more entries. + */ + if (sret == -EBUSY) + goto waitagain; + +out: + mutex_unlock(&iter->mutex); + + return sret; +} + +static int tracing_open_pipe(struct inode *inode, struct file *filp) +{ + struct ktap_trace_iterator *ktap_iter; + ktap_state *ks = inode->i_private; + + /* create a buffer to store the information to pass to userspace */ + ktap_iter = kzalloc(sizeof(*ktap_iter), GFP_KERNEL); + if (!ktap_iter) + return -ENOMEM; + + ktap_iter->private = ks; + ktap_iter->buffer = G(ks)->buffer; + ktap_iter->print_timestamp = G(ks)->parm->print_timestamp; + mutex_init(&ktap_iter->iter.mutex); + filp->private_data = &ktap_iter->iter; + + nonseekable_open(inode, filp); + + return 0; +} + +static int tracing_release_pipe(struct inode *inode, struct file *file) +{ + struct trace_iterator *iter = file->private_data; + struct ktap_trace_iterator *ktap_iter = KTAP_TRACE_ITER(iter); + + mutex_destroy(&iter->mutex); + kfree(ktap_iter); + return 0; +} + +static const struct file_operations tracing_pipe_fops = { + .open = tracing_open_pipe, + .read = tracing_read_pipe, + .splice_read = NULL, + .release = tracing_release_pipe, + .llseek = no_llseek, +}; + +/* + * print_backtrace maybe called from ktap mainthread, so be + * care on race with event closure thread. + * + * preempt disabled in ring_buffer_lock_reserve + * + * The implementation is similar with funtion __ftrace_trace_stack. + */ +void kp_transport_print_backtrace(ktap_state *ks) +{ + struct ring_buffer *buffer = G(ks)->buffer; + struct ring_buffer_event *event; + struct trace_entry *entry; + int size; + + size = KTAP_STACK_MAX_ENTRIES * sizeof(unsigned long); + event = ring_buffer_lock_reserve(buffer, sizeof(*entry) + size); + if (!event) { + return; + } else { + struct stack_trace trace; + + entry = ring_buffer_event_data(event); + tracing_generic_entry_update(entry, 0, 0); + entry->type = TRACE_STACK; + + trace.nr_entries = 0; + trace.skip = 10; + trace.max_entries = KTAP_STACK_MAX_ENTRIES; + trace.entries = (unsigned long *)(entry + 1); + save_stack_trace(&trace); + + ring_buffer_unlock_commit(buffer, event); + } + + return; +} + +void kp_transport_event_write(ktap_state *ks, struct ktap_event *e) +{ + struct ring_buffer *buffer = G(ks)->buffer; + struct ring_buffer_event *event; + struct trace_entry *entry; + + event = ring_buffer_lock_reserve(buffer, e->entry_size + + sizeof(struct ftrace_event_call *)); + if (!event) { + return; + } else { + entry = ring_buffer_event_data(event); + + memcpy(entry, e->entry, e->entry_size); + + ring_buffer_unlock_commit(buffer, event); + } +} + +void kp_transport_write(ktap_state *ks, const void *data, size_t length) +{ + struct ring_buffer *buffer = G(ks)->buffer; + struct ring_buffer_event *event; + struct trace_entry *entry; + int size; + + size = sizeof(struct trace_entry) + length; + + event = ring_buffer_lock_reserve(buffer, size); + if (!event) { + return; + } else { + entry = ring_buffer_event_data(event); + + tracing_generic_entry_update(entry, 0, 0); + entry->type = TRACE_PRINT; + memcpy(entry + 1, data, length); + + ring_buffer_unlock_commit(buffer, event); + } +} + +/* general print function */ +void kp_printf(ktap_state *ks, const char *fmt, ...) +{ + char buff[1024]; + va_list args; + int len; + + va_start(args, fmt); + len = vscnprintf(buff, 1024, fmt, args); + va_end(args); + + buff[len] = '\0'; + kp_transport_write(ks, buff, len + 1); +} + +void __kp_puts(ktap_state *ks, const char *str) +{ + kp_transport_write(ks, str, strlen(str) + 1); +} + +void __kp_bputs(ktap_state *ks, const char *str) +{ + struct ring_buffer *buffer = G(ks)->buffer; + struct ring_buffer_event *event; + struct trace_entry *entry; + int size; + + size = sizeof(struct trace_entry) + sizeof(unsigned long *); + + event = ring_buffer_lock_reserve(buffer, size); + if (!event) { + return; + } else { + entry = ring_buffer_event_data(event); + + tracing_generic_entry_update(entry, 0, 0); + entry->type = TRACE_BPUTS; + *(unsigned long *)(entry + 1) = (unsigned long)str; + + ring_buffer_unlock_commit(buffer, event); + } +} + +void kp_transport_exit(ktap_state *ks) +{ + ring_buffer_free(G(ks)->buffer); + debugfs_remove(G(ks)->trace_pipe_dentry); +} + +#define TRACE_BUF_SIZE_DEFAULT 1441792UL /* 16384 * 88 (sizeof(entry)) */ + +int kp_transport_init(ktap_state *ks, struct dentry *dir) +{ + struct ring_buffer *buffer; + struct dentry *dentry; + char filename[32] = {0}; + + ftrace_find_event = (void *)kallsyms_lookup_name("ftrace_find_event"); + if (!ftrace_find_event) { + printk("ktap: cannot lookup ftrace_find_event in kallsyms\n"); + return -EINVAL; + } + + buffer = ring_buffer_alloc(TRACE_BUF_SIZE_DEFAULT, RB_FL_OVERWRITE); + if (!buffer) + return -ENOMEM; + + sprintf(filename, "trace_pipe_%d", (int)task_tgid_vnr(current)); + + dentry = debugfs_create_file(filename, 0444, dir, + ks, &tracing_pipe_fops); + if (!dentry) { + pr_err("ktapvm: cannot create trace_pipe file in debugfs\n"); + ring_buffer_free(buffer); + return -1; + } + + G(ks)->buffer = buffer; + G(ks)->trace_pipe_dentry = dentry; + + return 0; +} + diff --git a/drivers/staging/ktap/interpreter/tstring.c b/drivers/staging/ktap/interpreter/tstring.c new file mode 100644 index 000000000000..ce4c88df2cc8 --- /dev/null +++ b/drivers/staging/ktap/interpreter/tstring.c @@ -0,0 +1,287 @@ +/* + * tstring.c - ktap tstring data struction manipulation function + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * Copyright (C) 1994-2013 Lua.org, PUC-Rio. + * - The part of code in this file is copied from lua initially. + * - lua's MIT license is compatible with GPL. + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef __KERNEL__ +#include "../include/ktap.h" +#else +#include "../include/ktap_types.h" +#endif + +#define STRING_MAXSHORTLEN 40 + +int kp_tstring_cmp(const ktap_string *ls, const ktap_string *rs) +{ + const char *l = getstr(ls); + size_t ll = ls->tsv.len; + const char *r = getstr(rs); + size_t lr = rs->tsv.len; + + for (;;) { + int temp = strcmp(l, r); + if (temp != 0) + return temp; + else { + /* strings are equal up to a `\0' */ + + /* index of first `\0' in both strings */ + size_t len = strlen(l); + + /* r is finished? */ + if (len == lr) + return (len == ll) ? 0 : 1; + else if (len == ll) /* l is finished? */ + return -1; + + /* + * both strings longer than `len'; + * go on comparing (after the `\0') + */ + len++; + l += len; ll -= len; r += len; lr -= len; + } + } +} + +/* + * equality for long strings + */ +int kp_tstring_eqlngstr(ktap_string *a, ktap_string *b) +{ + size_t len = a->tsv.len; + + return (a == b) || ((len == b->tsv.len) && + (memcmp(getstr(a), getstr(b), len) == 0)); +} + +/* + * equality for strings + */ +int kp_tstring_eqstr(ktap_string *a, ktap_string *b) +{ + return (a->tsv.tt == b->tsv.tt) && + (a->tsv.tt == KTAP_TSHRSTR ? eqshrstr(a, b) : + kp_tstring_eqlngstr(a, b)); +} + +#define STRING_HASHLIMIT 5 +unsigned int kp_string_hash(const char *str, size_t l, unsigned int seed) +{ + unsigned int h = seed ^ l; + size_t l1; + size_t step = (l >> STRING_HASHLIMIT) + 1; + + for (l1 = l; l1 >= step; l1 -= step) + h = h ^ ((h<<5) + (h>>2) + (u8)(str[l1 - 1])); + + return h; +} + + +/* + * resizes the string table + */ +void kp_tstring_resize(ktap_state *ks, int newsize) +{ + int i; + ktap_stringtable *tb = &G(ks)->strt; + + if (newsize > tb->size) { + kp_realloc(ks, tb->hash, tb->size, newsize, ktap_gcobject *); + + for (i = tb->size; i < newsize; i++) + tb->hash[i] = NULL; + } + + /* rehash */ + for (i = 0; i < tb->size; i++) { + ktap_gcobject *p = tb->hash[i]; + tb->hash[i] = NULL; + + while (p) { + ktap_gcobject *next = gch(p)->next; + unsigned int h = lmod(gco2ts(p)->hash, newsize); + + gch(p)->next = tb->hash[h]; + tb->hash[h] = p; + p = next; + } + } + + if (newsize < tb->size) { + /* shrinking slice must be empty */ + kp_realloc(ks, tb->hash, tb->size, newsize, ktap_gcobject *); + } + + tb->size = newsize; +} + +/* + * creates a new string object + */ +static ktap_string *createstrobj(ktap_state *ks, const char *str, size_t l, + int tag, unsigned int h, ktap_gcobject **list) +{ + ktap_string *ts; + size_t totalsize; /* total size of TString object */ + + totalsize = sizeof(ktap_string) + ((l + 1) * sizeof(char)); + ts = &kp_newobject(ks, tag, totalsize, list)->ts; + ts->tsv.len = l; + ts->tsv.hash = h; + ts->tsv.extra = 0; + memcpy(ts + 1, str, l * sizeof(char)); + ((char *)(ts + 1))[l] = '\0'; /* ending 0 */ + return ts; +} + +/* + * creates a new short string, inserting it into string table + */ +static ktap_string *newshrstr(ktap_state *ks, const char *str, size_t l, + unsigned int h) +{ + ktap_gcobject **list; + ktap_stringtable *tb = &G(ks)->strt; + ktap_string *s; + + if (tb->nuse >= (int)tb->size) + kp_tstring_resize(ks, tb->size * 2); /* too crowded */ + + list = &tb->hash[lmod(h, tb->size)]; + s = createstrobj(ks, str, l, KTAP_TSHRSTR, h, list); + tb->nuse++; + return s; +} + +#ifdef __KERNEL__ +static arch_spinlock_t tstring_lock = + (arch_spinlock_t)__ARCH_SPIN_LOCK_UNLOCKED; +#endif + +/* + * checks whether short string exists and reuses it or creates a new one + */ +static ktap_string *internshrstr(ktap_state *ks, const char *str, size_t l) +{ + ktap_gcobject *o; + ktap_global_state *g = G(ks); + ktap_string *ts; + unsigned int h = kp_string_hash(str, l, g->seed); + unsigned long __maybe_unused flags; + +#ifdef __KERNEL__ + local_irq_save(flags); + arch_spin_lock(&tstring_lock); +#endif + + for (o = g->strt.hash[lmod(h, g->strt.size)]; o != NULL; + o = gch(o)->next) { + ts = rawgco2ts(o); + + if (h == ts->tsv.hash && ts->tsv.len == l && + (memcmp(str, getstr(ts), l * sizeof(char)) == 0)) + goto out; + } + + ts = newshrstr(ks, str, l, h); /* not found; create a new string */ + + out: +#ifdef __KERNEL__ + arch_spin_unlock(&tstring_lock); + local_irq_restore(flags); +#endif + return ts; +} + + +/* + * new string (with explicit length) + */ +ktap_string *kp_tstring_newlstr(ktap_state *ks, const char *str, size_t l) +{ + /* short string? */ + if (l <= STRING_MAXSHORTLEN) + return internshrstr(ks, str, l); + else + return createstrobj(ks, str, l, KTAP_TLNGSTR, G(ks)->seed, + NULL); +} + +ktap_string *kp_tstring_newlstr_local(ktap_state *ks, const char *str, size_t l) +{ + return createstrobj(ks, str, l, KTAP_TLNGSTR, G(ks)->seed, + &ks->gclist); +} + +/* + * new zero-terminated string + */ +ktap_string *kp_tstring_new(ktap_state *ks, const char *str) +{ + return kp_tstring_newlstr(ks, str, strlen(str)); +} + +ktap_string *kp_tstring_new_local(ktap_state *ks, const char *str) +{ + return createstrobj(ks, str, strlen(str), KTAP_TLNGSTR, G(ks)->seed, + &ks->gclist); +} + +void kp_tstring_freeall(ktap_state *ks) +{ + ktap_global_state *g = G(ks); + int h; + + for (h = 0; h < g->strt.size; h++) { + ktap_gcobject *o, *next; + o = g->strt.hash[h]; + while (o) { + next = gch(o)->next; + kp_free(ks, o); + o = next; + } + g->strt.hash[h] = NULL; + } + + kp_free(ks, g->strt.hash); +} + +/* todo: dump long string, strt table only contain short string */ +void kp_tstring_dump(ktap_state *ks) +{ + ktap_gcobject *o; + ktap_global_state *g = G(ks); + int h; + + kp_printf(ks, "tstring dump: strt size: %d, nuse: %d\n", g->strt.size, + g->strt.nuse); + for (h = 0; h < g->strt.size; h++) { + for (o = g->strt.hash[h]; o != NULL; o = gch(o)->next) { + ktap_string *ts = rawgco2ts(o); + kp_printf(ks, "%s [%d]\n", getstr(ts), (int)ts->tsv.len); + } + } +} + diff --git a/drivers/staging/ktap/interpreter/vm.c b/drivers/staging/ktap/interpreter/vm.c new file mode 100644 index 000000000000..bc7b9518a88f --- /dev/null +++ b/drivers/staging/ktap/interpreter/vm.c @@ -0,0 +1,1369 @@ +/* + * vm.c - ktap script virtual machine in Linux kernel + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * Copyright (C) 1994-2013 Lua.org, PUC-Rio. + * - The part of code in this file is copied from lua initially. + * - lua's MIT license is compatible with GPL. + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include "../include/ktap.h" + +#define KTAP_MINSTACK 20 + +/* todo: enlarge maxstack for big system like 64-bit */ +#define KTAP_MAXSTACK 15000 + +#define KTAP_STACK_SIZE (BASIC_STACK_SIZE * sizeof(ktap_value)) + +#define CIST_KTAP (1 << 0) /* call is running a ktap function */ +#define CIST_REENTRY (1 << 2) + +#define isktapfunc(ci) ((ci)->callstatus & CIST_KTAP) + +static void ktap_concat(ktap_state *ks, int start, int end) +{ + int i, len = 0; + StkId top = ks->ci->u.l.base; + ktap_string *ts; + char *ptr, *buffer; + + for (i = start; i <= end; i++) { + if (!ttisstring(top + i)) { + kp_error(ks, "cannot concat non-string\n"); + setnilvalue(top + start); + return; + } + + len += rawtsvalue(top + i)->tsv.len; + } + + if (len >= KTAP_PERCPU_BUFFER_SIZE) { + kp_error(ks, "Error: too long string concatenation\n"); + return; + } + + preempt_disable_notrace(); + + buffer = kp_percpu_data(KTAP_PERCPU_DATA_BUFFER); + ptr = buffer; + + for (i = start; i <= end; i++) { + int len = rawtsvalue(top + i)->tsv.len; + strncpy(ptr, svalue(top + i), len); + ptr += len; + } + ts = kp_tstring_newlstr(ks, buffer, len); + setsvalue(top + start, ts); + + preempt_enable_notrace(); +} + +/* todo: compare l == r if both is tstring type? */ +static int lessthan(ktap_state *ks, const ktap_value *l, const ktap_value *r) +{ + if (ttisnumber(l) && ttisnumber(r)) + return NUMLT(nvalue(l), nvalue(r)); + else if (ttisstring(l) && ttisstring(r)) + return kp_tstring_cmp(rawtsvalue(l), rawtsvalue(r)) < 0; + + return 0; +} + +static int lessequal(ktap_state *ks, const ktap_value *l, const ktap_value *r) +{ + if (ttisnumber(l) && ttisnumber(r)) + return NUMLE(nvalue(l), nvalue(r)); + else if (ttisstring(l) && ttisstring(r)) + return kp_tstring_cmp(rawtsvalue(l), rawtsvalue(r)) <= 0; + + return 0; +} + +static int fb2int (int x) +{ + int e = (x >> 3) & 0x1f; + if (e == 0) + return x; + else + return ((x & 7) + 8) << (e - 1); +} + +static const ktap_value *ktap_tonumber(const ktap_value *obj, ktap_value *n) +{ + if (ttisnumber(obj)) + return obj; + + return NULL; +} + +static ktap_upval *findupval(ktap_state *ks, StkId level) +{ + ktap_global_state *g = G(ks); + ktap_gcobject **pp = &ks->openupval; + ktap_upval *p; + ktap_upval *uv; + + while (*pp != NULL && (p = gco2uv(*pp))->v >= level) { + if (p->v == level) { /* found a corresponding upvalue? */ + return p; + } + pp = &p->next; + } + + /* not found: create a new one */ + uv = &kp_newobject(ks, KTAP_TUPVAL, sizeof(ktap_upval), pp)->uv; + uv->v = level; /* current value lives in the stack */ + uv->u.l.prev = &g->uvhead; /* double link it in `uvhead' list */ + uv->u.l.next = g->uvhead.u.l.next; + uv->u.l.next->u.l.prev = uv; + g->uvhead.u.l.next = uv; + return uv; +} + +/* todo: implement this*/ +static void function_close (ktap_state *ks, StkId level) +{ +} + +/* create a new closure */ +static void pushclosure(ktap_state *ks, ktap_proto *p, ktap_upval **encup, + StkId base, StkId ra) +{ + int nup = p->sizeupvalues; + ktap_upvaldesc *uv = p->upvalues; + int i; + ktap_closure *ncl = kp_newlclosure(ks, nup); + + ncl->l.p = p; + setcllvalue(ra, ncl); /* anchor new closure in stack */ + + /* fill in its upvalues */ + for (i = 0; i < nup; i++) { + if (uv[i].instack) { + /* upvalue refers to local variable? */ + ncl->l.upvals[i] = findupval(ks, base + uv[i].idx); + } else { + /* get upvalue from enclosing function */ + ncl->l.upvals[i] = encup[uv[i].idx]; + } + } + //p->cache = ncl; /* save it on cache for reuse */ +} + +static void gettable(ktap_state *ks, const ktap_value *t, ktap_value *key, + StkId val) +{ + if (ttistable(t)) { + setobj(val, kp_table_get(hvalue(t), key)); + } else if (ttisaggrtable(t)) { + kp_aggrtable_get(ks, ahvalue(t), key, val); + } else { + kp_error(ks, "get key from non-table\n"); + } +} + +static void settable(ktap_state *ks, const ktap_value *t, ktap_value *key, + StkId val) +{ + if (ttistable(t)) { + kp_table_setvalue(ks, hvalue(t), key, val); + } else if (ttisaggrtable(t)) { + kp_aggrtable_set(ks, ahvalue(t), key, val); + } else { + kp_error(ks, "set key to non-table\n"); + } +} + +static void settable_incr(ktap_state *ks, const ktap_value *t, ktap_value *key, + StkId val) +{ + if (unlikely(!ttistable(t))) { + kp_error(ks, "use += operator for non-table\n"); + return; + } + + if (unlikely(!ttisnumber(val))) { + kp_error(ks, "use non-number to += operator\n"); + return; + } + + kp_table_atomic_inc(ks, hvalue(t), key, nvalue(val)); +} + + +static void growstack(ktap_state *ks, int n) +{ + ktap_value *oldstack; + int lim; + ktap_callinfo *ci; + ktap_gcobject *up; + int size = ks->stacksize; + int needed = (int)(ks->top - ks->stack) + n; + int newsize = 2 * size; + + if (newsize > KTAP_MAXSTACK) + newsize = KTAP_MAXSTACK; + + if (newsize < needed) + newsize = needed; + + if (newsize > KTAP_MAXSTACK) { /* stack overflow? */ + kp_error(ks, "stack overflow\n"); + return; + } + + /* realloc stack */ + oldstack = ks->stack; + lim = ks->stacksize; + kp_realloc(ks, ks->stack, ks->stacksize, newsize, ktap_value); + + for (; lim < newsize; lim++) + setnilvalue(ks->stack + lim); + ks->stacksize = newsize; + ks->stack_last = ks->stack + newsize; + + /* correct stack */ + ks->top = (ks->top - oldstack) + ks->stack; + for (up = ks->openupval; up != NULL; up = up->gch.next) + gco2uv(up)->v = (gco2uv(up)->v - oldstack) + ks->stack; + + for (ci = ks->ci; ci != NULL; ci = ci->prev) { + ci->top = (ci->top - oldstack) + ks->stack; + ci->func = (ci->func - oldstack) + ks->stack; + if (isktapfunc(ci)) + ci->u.l.base = (ci->u.l.base - oldstack) + ks->stack; + } + +} + +static inline void checkstack(ktap_state *ks, int n) +{ + if (ks->stack_last - ks->top <= n) + growstack(ks, n); +} + +static StkId adjust_varargs(ktap_state *ks, ktap_proto *p, int actual) +{ + int i; + int nfixargs = p->numparams; + StkId base, fixed; + + /* move fixed parameters to final position */ + fixed = ks->top - actual; /* first fixed argument */ + base = ks->top; /* final position of first argument */ + + for (i=0; i < nfixargs; i++) { + setobj(ks->top++, fixed + i); + setnilvalue(fixed + i); + } + + return base; +} + +static int poscall(ktap_state *ks, StkId first_result) +{ + ktap_callinfo *ci; + StkId res; + int wanted, i; + + ci = ks->ci; + + res = ci->func; + wanted = ci->nresults; + + ks->ci = ci = ci->prev; + + for (i = wanted; i != 0 && first_result < ks->top; i--) + setobj(res++, first_result++); + + while(i-- > 0) + setnilvalue(res++); + + ks->top = res; + + return (wanted - (-1)); +} + +static ktap_callinfo *extend_ci(ktap_state *ks) +{ + ktap_callinfo *ci; + + ci = kp_malloc(ks, sizeof(ktap_callinfo)); + ks->ci->next = ci; + ci->prev = ks->ci; + ci->next = NULL; + + return ci; +} + +static void free_ci(ktap_state *ks) +{ + ktap_callinfo *ci = ks->ci; + ktap_callinfo *next; + + if (!ci) + return; + + next = ci->next; + ci->next = NULL; + while ((ci = next) != NULL) { + next = ci->next; + kp_free(ks, ci); + } +} + +#define next_ci(ks) (ks->ci = ks->ci->next ? ks->ci->next : extend_ci(ks)) +#define savestack(ks, p) ((char *)(p) - (char *)ks->stack) +#define restorestack(ks, n) ((ktap_value *)((char *)ks->stack + (n))) + +static int precall(ktap_state *ks, StkId func, int nresults) +{ + ktap_cfunction f; + ktap_callinfo *ci; + ktap_proto *p; + StkId base; + ptrdiff_t funcr = savestack(ks, func); + int n; + + switch (ttype(func)) { + case KTAP_TLCF: /* light C function */ + f = fvalue(func); + goto CFUNC; + case KTAP_TCCL: /* C closure */ + f = clcvalue(func)->f; + CFUNC: + checkstack(ks, KTAP_MINSTACK); + ci = next_ci(ks); + ci->nresults = nresults; + ci->func = restorestack(ks, funcr); + ci->top = ks->top + KTAP_MINSTACK; + ci->callstatus = 0; + n = (*f)(ks); + poscall(ks, ks->top - n); + return 1; + case KTAP_TLCL: + p = CLVALUE(func)->p; + checkstack(ks, p->maxstacksize); + func = restorestack(ks, funcr); + n = (int)(ks->top - func) - 1; /* number of real arguments */ + + /* complete missing arguments */ + for (; n < p->numparams; n++) + setnilvalue(ks->top++); + + base = (!p->is_vararg) ? func + 1 : adjust_varargs(ks, p, n); + ci = next_ci(ks); + ci->nresults = nresults; + ci->func = func; + ci->u.l.base = base; + ci->top = base + p->maxstacksize; + ci->u.l.savedpc = p->code; /* starting point */ + ci->callstatus = CIST_KTAP; + ks->top = ci->top; + return 0; + default: + kp_error(ks, "attempt to call nil function\n"); + } + + return 0; +} + +#define RA(i) (base+GETARG_A(i)) +#define RB(i) (base+GETARG_B(i)) +#define ISK(x) ((x) & BITRK) +#define RC(i) base+GETARG_C(i) +#define RKB(i) \ + ISK(GETARG_B(i)) ? k+INDEXK(GETARG_B(i)) : base+GETARG_B(i) +#define RKC(i) \ + ISK(GETARG_C(i)) ? k+INDEXK(GETARG_C(i)) : base+GETARG_C(i) + +#define dojump(ci,i,e) { \ + ci->u.l.savedpc += GETARG_sBx(i) + e; } +#define donextjump(ci) { instr = *ci->u.l.savedpc; dojump(ci, instr, 1); } + +#define arith_op(ks, op) { \ + ktap_value *rb = RKB(instr); \ + ktap_value *rc = RKC(instr); \ + if (ttisnumber(rb) && ttisnumber(rc)) { \ + ktap_number nb = nvalue(rb), nc = nvalue(rc); \ + setnvalue(ra, op(nb, nc)); \ + } else { \ + kp_puts(ks, "Error: Cannot make arith operation\n"); \ + return; \ + } } + +static ktap_value *cfunction_cache_get(ktap_state *ks, int index); + +static void ktap_execute(ktap_state *ks) +{ + int exec_count = 0; + ktap_callinfo *ci; + ktap_lclosure *cl; + ktap_value *k; + unsigned int instr, opcode; + StkId base; /* stack pointer */ + StkId ra; /* register pointer */ + int res, nresults; /* temp varible */ + + ci = ks->ci; + + newframe: + cl = CLVALUE(ci->func); + k = cl->p->k; + base = ci->u.l.base; + + mainloop: + /* main loop of interpreter */ + + /* dead loop detaction */ + if (exec_count++ == kp_max_exec_count) { + if (G(ks)->mainthread != ks) { + kp_error(ks, "non-mainthread executed instructions " + "exceed max limit(%d)\n", + kp_max_exec_count); + return; + } + + cond_resched(); + if (signal_pending(current)) { + flush_signals(current); + return; + } + exec_count = 0; + } + + instr = *(ci->u.l.savedpc++); + opcode = GET_OPCODE(instr); + + /* ra is target register */ + ra = RA(instr); + + switch (opcode) { + case OP_MOVE: + setobj(ra, base + GETARG_B(instr)); + break; + case OP_LOADK: + setobj(ra, k + GETARG_Bx(instr)); + break; + case OP_LOADKX: + setobj(ra, k + GETARG_Ax(*ci->u.l.savedpc++)); + break; + case OP_LOADBOOL: + setbvalue(ra, GETARG_B(instr)); + if (GETARG_C(instr)) + ci->u.l.savedpc++; + break; + case OP_LOADNIL: { + int b = GETARG_B(instr); + do { + setnilvalue(ra++); + } while (b--); + break; + } + case OP_GETUPVAL: { + int b = GETARG_B(instr); + setobj(ra, cl->upvals[b]->v); + break; + } + case OP_GETTABUP: { + int b = GETARG_B(instr); + gettable(ks, cl->upvals[b]->v, RKC(instr), ra); + base = ci->u.l.base; + break; + } + case OP_GETTABLE: + gettable(ks, RB(instr), RKC(instr), ra); + base = ci->u.l.base; + break; + case OP_SETTABUP: { + int a = GETARG_A(instr); + settable(ks, cl->upvals[a]->v, RKB(instr), RKC(instr)); + base = ci->u.l.base; + break; + } + case OP_SETTABUP_INCR: { + int a = GETARG_A(instr); + settable_incr(ks, cl->upvals[a]->v, RKB(instr), RKC(instr)); + base = ci->u.l.base; + break; + } + case OP_SETUPVAL: { + ktap_upval *uv = cl->upvals[GETARG_B(instr)]; + setobj(uv->v, ra); + break; + } + case OP_SETTABLE: + settable(ks, ra, RKB(instr), RKC(instr)); + base = ci->u.l.base; + break; + case OP_SETTABLE_INCR: + settable_incr(ks, ra, RKB(instr), RKC(instr)); + base = ci->u.l.base; + break; + case OP_NEWTABLE: { + int b = GETARG_B(instr); + int c = GETARG_C(instr); + ktap_table *t = kp_table_new(ks); + sethvalue(ra, t); + if (b != 0 || c != 0) + kp_table_resize(ks, t, fb2int(b), fb2int(c)); + break; + } + case OP_SELF: { + StkId rb = RB(instr); + setobj(ra+1, rb); + gettable(ks, rb, RKC(instr), ra); + base = ci->u.l.base; + break; + } + case OP_ADD: + arith_op(ks, NUMADD); + break; + case OP_SUB: + arith_op(ks, NUMSUB); + break; + case OP_MUL: + arith_op(ks, NUMMUL); + break; + case OP_DIV: + /* divide 0 checking */ + if (!nvalue(RKC(instr))) { + kp_error(ks, "divide 0 arith operation\n"); + return; + } + arith_op(ks, NUMDIV); + break; + case OP_MOD: + /* divide 0 checking */ + if (!nvalue(RKC(instr))) { + kp_error(ks, "mod 0 arith operation\n"); + return; + } + arith_op(ks, NUMMOD); + break; + case OP_POW: + kp_error(ks, "ktap don't support pow arith in kernel\n"); + return; + case OP_UNM: { + ktap_value *rb = RB(instr); + if (ttisnumber(rb)) { + ktap_number nb = nvalue(rb); + setnvalue(ra, NUMUNM(nb)); + } + break; + } + case OP_NOT: + res = isfalse(RB(instr)); + setbvalue(ra, res); + break; + case OP_LEN: { + int len = kp_objlen(ks, RB(instr)); + if (len < 0) + return; + setnvalue(ra, len); + break; + } + case OP_CONCAT: { + int b = GETARG_B(instr); + int c = GETARG_C(instr); + ktap_concat(ks, b, c); + break; + } + case OP_JMP: + dojump(ci, instr, 0); + break; + case OP_EQ: { + ktap_value *rb = RKB(instr); + ktap_value *rc = RKC(instr); + if ((int)equalobj(ks, rb, rc) != GETARG_A(instr)) + ci->u.l.savedpc++; + else + donextjump(ci); + + base = ci->u.l.base; + break; + } + case OP_LT: + if (lessthan(ks, RKB(instr), RKC(instr)) != GETARG_A(instr)) + ci->u.l.savedpc++; + else + donextjump(ci); + base = ci->u.l.base; + break; + case OP_LE: + if (lessequal(ks, RKB(instr), RKC(instr)) != GETARG_A(instr)) + ci->u.l.savedpc++; + else + donextjump(ci); + base = ci->u.l.base; + break; + case OP_TEST: + if (GETARG_C(instr) ? isfalse(ra) : !isfalse(ra)) + ci->u.l.savedpc++; + else + donextjump(ci); + break; + case OP_TESTSET: { + ktap_value *rb = RB(instr); + if (GETARG_C(instr) ? isfalse(rb) : !isfalse(rb)) + ci->u.l.savedpc++; + else { + setobj(ra, rb); + donextjump(ci); + } + break; + } + case OP_CALL: { + int b = GETARG_B(instr); + int ret; + + nresults = GETARG_C(instr) - 1; + + if (b != 0) + ks->top = ra + b; + + ret = precall(ks, ra, nresults); + if (ret) { /* C function */ + if (nresults >= 0) + ks->top = ci->top; + base = ci->u.l.base; + break; + } else { /* ktap function */ + ci = ks->ci; + /* this flag is used for return time, see OP_RETURN */ + ci->callstatus |= CIST_REENTRY; + goto newframe; + } + break; + } + case OP_TAILCALL: { + int b = GETARG_B(instr); + + if (b != 0) + ks->top = ra+b; + if (precall(ks, ra, -1)) /* C function? */ + base = ci->u.l.base; + else { + int aux; + + /* + * tail call: put called frame (n) in place of + * caller one (o) + */ + ktap_callinfo *nci = ks->ci; /* called frame */ + ktap_callinfo *oci = nci->prev; /* caller frame */ + StkId nfunc = nci->func; /* called function */ + StkId ofunc = oci->func; /* caller function */ + /* last stack slot filled by 'precall' */ + StkId lim = nci->u.l.base + + CLVALUE(nfunc)->p->numparams; + + /* close all upvalues from previous call */ + if (cl->p->sizep > 0) + function_close(ks, oci->u.l.base); + + /* move new frame into old one */ + for (aux = 0; nfunc + aux < lim; aux++) + setobj(ofunc + aux, nfunc + aux); + /* correct base */ + oci->u.l.base = ofunc + (nci->u.l.base - nfunc); + /* correct top */ + oci->top = ks->top = ofunc + (ks->top - nfunc); + oci->u.l.savedpc = nci->u.l.savedpc; + /* remove new frame */ + ci = ks->ci = oci; + /* restart ktap_execute over new ktap function */ + goto newframe; + } + break; + } + case OP_RETURN: { + int b = GETARG_B(instr); + if (b != 0) + ks->top = ra+b-1; + if (cl->p->sizep > 0) + function_close(ks, base); + b = poscall(ks, ra); + + /* if it's called from external invocation, just return */ + if (!(ci->callstatus & CIST_REENTRY)) + return; + + ci = ks->ci; + if (b) + ks->top = ci->top; + goto newframe; + } + case OP_FORLOOP: { + ktap_number step = nvalue(ra+2); + /* increment index */ + ktap_number idx = NUMADD(nvalue(ra), step); + ktap_number limit = nvalue(ra+1); + if (NUMLT(0, step) ? NUMLE(idx, limit) : NUMLE(limit, idx)) { + ci->u.l.savedpc += GETARG_sBx(instr); /* jump back */ + setnvalue(ra, idx); /* update internal index... */ + setnvalue(ra+3, idx); /* ...and external index */ + } + break; + } + case OP_FORPREP: { + const ktap_value *init = ra; + const ktap_value *plimit = ra + 1; + const ktap_value *pstep = ra + 2; + + if (!ktap_tonumber(init, ra)) { + kp_error(ks, KTAP_QL("for") + " initial value must be a number\n"); + return; + } else if (!ktap_tonumber(plimit, ra + 1)) { + kp_error(ks, KTAP_QL("for") + " limit must be a number\n"); + return; + } else if (!ktap_tonumber(pstep, ra + 2)) { + kp_error(ks, KTAP_QL("for") " step must be a number\n"); + return; + } + + setnvalue(ra, NUMSUB(nvalue(ra), nvalue(pstep))); + ci->u.l.savedpc += GETARG_sBx(instr); + break; + } + case OP_TFORCALL: { + StkId cb = ra + 3; /* call base */ + setobj(cb + 2, ra + 2); + setobj(cb + 1, ra + 1); + setobj(cb, ra); + ks->top = cb + 3; /* func. + 2 args (state and index) */ + kp_call(ks, cb, GETARG_C(instr)); + base = ci->u.l.base; + ks->top = ci->top; + instr = *(ci->u.l.savedpc++); /* go to next instruction */ + ra = RA(instr); + } + /*go through */ + case OP_TFORLOOP: + if (!ttisnil(ra + 1)) { /* continue loop? */ + setobj(ra, ra + 1); /* save control variable */ + ci->u.l.savedpc += GETARG_sBx(instr); /* jump back */ + } + break; + case OP_SETLIST: { + int n = GETARG_B(instr); + int c = GETARG_C(instr); + int last; + ktap_table *h; + + if (n == 0) + n = (int)(ks->top - ra) - 1; + if (c == 0) + c = GETARG_Ax(*ci->u.l.savedpc++); + + h = hvalue(ra); + last = ((c - 1) * LFIELDS_PER_FLUSH) + n; + if (last > h->sizearray) /* needs more space? */ + kp_table_resizearray(ks, h, last); + + for (; n > 0; n--) { + ktap_value *val = ra+n; + kp_table_setint(ks, h, last--, val); + } + /* correct top (in case of previous open call) */ + ks->top = ci->top; + break; + } + case OP_CLOSURE: { + /* need to use closure cache? (multithread contention issue)*/ + ktap_proto *p = cl->p->p[GETARG_Bx(instr)]; + pushclosure(ks, p, cl->upvals, base, ra); + break; + } + case OP_VARARG: { + int b = GETARG_B(instr) - 1; + int j; + int n = (int)(base - ci->func) - cl->p->numparams - 1; + if (b < 0) { /* B == 0? */ + b = n; /* get all var. arguments */ + checkstack(ks, n); + /* previous call may change the stack */ + ra = RA(instr); + ks->top = ra + n; + } + for (j = 0; j < b; j++) { + if (j < n) { + setobj(ra + j, base - n + j); + } else + setnilvalue(ra + j); + } + break; + } + case OP_EXTRAARG: + return; + + case OP_EVENT: { + struct ktap_event *e = ks->current_event; + + if (unlikely(!e)) { + kp_error(ks, "invalid event context\n"); + return; + } + setevalue(ra, e); + break; + } + + case OP_EVENTNAME: { + struct ktap_event *e = ks->current_event; + + if (unlikely(!e)) { + kp_error(ks, "invalid event context\n"); + return; + } + setsvalue(ra, kp_tstring_new(ks, e->call->name)); + break; + } + case OP_EVENTARG: + if (unlikely(!ks->current_event)) { + kp_error(ks, "invalid event context\n"); + return; + } + + kp_event_getarg(ks, ra, GETARG_B(instr)); + break; + case OP_LOAD_GLOBAL: { + ktap_value *cfunc = cfunction_cache_get(ks, GETARG_C(instr)); + setobj(ra, cfunc); + } + break; + + case OP_EXIT: + return; + } + + goto mainloop; +} + +void kp_call(ktap_state *ks, StkId func, int nresults) +{ + if (!precall(ks, func, nresults)) + ktap_execute(ks); +} + +static int cfunction_cache_getindex(ktap_state *ks, ktap_value *fname); + +/* + * This function must be called before all code loaded. + */ +void kp_optimize_code(ktap_state *ks, int level, ktap_proto *f) +{ + int i; + + for (i = 0; i < f->sizecode; i++) { + int instr = f->code[i]; + ktap_value *k = f->k; + + if (GET_OPCODE(instr) == OP_GETTABUP) { + if ((GETARG_B(instr) == 0) && ISK(GETARG_C(instr))) { + ktap_value *field = k + INDEXK(GETARG_C(instr)); + if (ttype(field) == KTAP_TSTRING) { + int index = cfunction_cache_getindex(ks, + field); + if (index == -1) + break; + + SET_OPCODE(instr, OP_LOAD_GLOBAL); + SETARG_C(instr, index); + f->code[i] = instr; + break; + } + } + } + } + + /* continue optimize sub functions */ + for (i = 0; i < f->sizep; i++) + kp_optimize_code(ks, level + 1, f->p[i]); +} + +static ktap_value *cfunction_cache_get(ktap_state *ks, int index) +{ + return &G(ks)->cfunction_tbl[index]; +} + +static int cfunction_cache_getindex(ktap_state *ks, ktap_value *fname) +{ + const ktap_value *gt = kp_table_getint(hvalue(&G(ks)->registry), + KTAP_RIDX_GLOBALS); + const ktap_value *cfunc; + int nr, i; + + nr = G(ks)->nr_builtin_cfunction; + cfunc = kp_table_get(hvalue(gt), fname); + + for (i = 0; i < nr; i++) { + if (rawequalobj(&G(ks)->cfunction_tbl[i], cfunc)) + return i; + } + + return -1; +} + +static void cfunction_cache_add(ktap_state *ks, ktap_value *func) +{ + int nr = G(ks)->nr_builtin_cfunction; + setobj(&G(ks)->cfunction_tbl[nr], func); + G(ks)->nr_builtin_cfunction++; +} + +static void cfunction_cache_exit(ktap_state *ks) +{ + kp_free(ks, G(ks)->cfunction_tbl); +} + +static int cfunction_cache_init(ktap_state *ks) +{ + G(ks)->cfunction_tbl = kp_zalloc(ks, sizeof(ktap_value) * 128); + if (!G(ks)->cfunction_tbl) + return -ENOMEM; + + return 0; +} + +/* function for register library */ +void kp_register_lib(ktap_state *ks, const char *libname, const ktap_Reg *funcs) +{ + int i; + ktap_table *target_tbl; + const ktap_value *gt = kp_table_getint(hvalue(&G(ks)->registry), + KTAP_RIDX_GLOBALS); + + /* lib is null when register baselib function */ + if (libname == NULL) + target_tbl = hvalue(gt); + else { + ktap_value key, val; + + target_tbl = kp_table_new(ks); + kp_table_resize(ks, target_tbl, 0, + sizeof(*funcs) / sizeof(ktap_Reg)); + + setsvalue(&key, kp_tstring_new(ks, libname)); + sethvalue(&val, target_tbl); + kp_table_setvalue(ks, hvalue(gt), &key, &val); + } + + for (i = 0; funcs[i].name != NULL; i++) { + ktap_value func_name, cl; + + setsvalue(&func_name, kp_tstring_new(ks, funcs[i].name)); + setfvalue(&cl, funcs[i].func); + kp_table_setvalue(ks, target_tbl, &func_name, &cl); + + cfunction_cache_add(ks, &cl); + } +} + +#define BASIC_STACK_SIZE (2 * KTAP_MINSTACK) + +static void kp_init_registry(ktap_state *ks) +{ + ktap_value mt; + ktap_table *registry = kp_table_new(ks); + + sethvalue(&G(ks)->registry, registry); + kp_table_resize(ks, registry, KTAP_RIDX_LAST, 0); + setthvalue(ks, &mt, ks); + kp_table_setint(ks, registry, KTAP_RIDX_MAINTHREAD, &mt); + sethvalue(&mt, kp_table_new(ks)); + kp_table_setint(ks, registry, KTAP_RIDX_GLOBALS, &mt); +} + +static int kp_init_arguments(ktap_state *ks, int argc, char __user **user_argv) +{ + const ktap_value *gt = kp_table_getint(hvalue(&G(ks)->registry), + KTAP_RIDX_GLOBALS); + ktap_table *global_tbl = hvalue(gt); + ktap_table *arg_tbl = kp_table_new(ks); + ktap_value arg_tblval; + ktap_value arg_tsval; + char **argv; + int i, ret; + + setsvalue(&arg_tsval, kp_tstring_new(ks, "arg")); + sethvalue(&arg_tblval, arg_tbl); + kp_table_setvalue(ks, global_tbl, &arg_tsval, &arg_tblval); + + if (!argc) + return 0; + + if (argc > 1024) + return -EINVAL; + + argv = kzalloc(argc * sizeof(char *), GFP_KERNEL); + if (!argv) + return -ENOMEM; + + ret = copy_from_user(argv, user_argv, argc * sizeof(char *)); + if (ret < 0) { + kfree(argv); + return -EFAULT; + } + + kp_table_resize(ks, arg_tbl, argc, 1); + + ret = 0; + for (i = 0; i < argc; i++) { + ktap_value val; + char __user *ustr = argv[i]; + char * kstr; + int len; + int res; + + len = strlen_user(ustr); + if (len > 0x1000) { + ret = -EINVAL; + break; + } + + kstr = kmalloc(len + 1, GFP_KERNEL); + if (!kstr) { + ret = -ENOMEM; + break; + } + + if (strncpy_from_user(kstr, ustr, len) < 0) { + ret = -EFAULT; + break; + } + + kstr[len] = '\0'; + + if (!kstrtoint(kstr, 10, &res)) { + setnvalue(&val, res); + } else + setsvalue(&val, kp_tstring_new(ks, kstr)); + + kp_table_setint(ks, arg_tbl, i, &val); + + kfree(kstr); + } + + kfree(argv); + return ret; +} + +DEFINE_PER_CPU(int, kp_recursion_context[PERF_NR_CONTEXTS]); + +/* todo: make this per-session aware */ +static void __percpu *kp_pcpu_data[KTAP_PERCPU_DATA_MAX][PERF_NR_CONTEXTS]; + +void *kp_percpu_data(int type) +{ + return this_cpu_ptr(kp_pcpu_data[type][trace_get_context_bit()]); +} + +static void free_kp_percpu_data(void) +{ + int i, j; + + for (i = 0; i < KTAP_PERCPU_DATA_MAX; i++) { + for (j = 0; j < PERF_NR_CONTEXTS; j++) { + free_percpu(kp_pcpu_data[i][j]); + kp_pcpu_data[i][j] = NULL; + } + } +} + +static int alloc_kp_percpu_data(void) +{ + int data_size[KTAP_PERCPU_DATA_MAX] = { + sizeof(ktap_state), KTAP_STACK_SIZE, KTAP_PERCPU_BUFFER_SIZE, + KTAP_PERCPU_BUFFER_SIZE, sizeof(ktap_btrace)}; + int i, j; + + for (i = 0; i < KTAP_PERCPU_DATA_MAX; i++) { + for (j = 0; j < PERF_NR_CONTEXTS; j++) { + void __percpu *data = __alloc_percpu(data_size[i], + __alignof__(char)); + if (!data) + goto fail; + kp_pcpu_data[i][j] = data; + } + } + + return 0; + + fail: + free_kp_percpu_data(); + return -ENOMEM; +} + +static void kp_init_state(ktap_state *ks) +{ + ktap_callinfo *ci; + int i; + + ks->stacksize = BASIC_STACK_SIZE; + + for (i = 0; i < BASIC_STACK_SIZE; i++) + setnilvalue(ks->stack + i); + + ks->top = ks->stack; + ks->stack_last = ks->stack + ks->stacksize; + + ci = &ks->baseci; + ci->callstatus = 0; + ci->func = ks->top; + setnilvalue(ks->top++); + ci->top = ks->top + KTAP_MINSTACK; + ks->ci = ci; +} + +static void free_all_ci(ktap_state *ks) +{ + int cpu; + + for_each_possible_cpu(cpu) { + ktap_state *ks; + int j; + + for (j = 0; j < PERF_NR_CONTEXTS; j++) { + if (!kp_pcpu_data[KTAP_PERCPU_DATA_STATE][j]) + break; + + ks = per_cpu_ptr(kp_pcpu_data[KTAP_PERCPU_DATA_STATE][j], cpu); + if (!ks) + break; + + free_ci(ks); + } + } + + free_ci(ks); +} + +void kp_exitthread(ktap_state *ks) +{ + /* free local allocation objects, like annotate strings */ + kp_free_gclist(ks, ks->gclist); +} + +ktap_state *kp_newthread(ktap_state *mainthread) +{ + ktap_state *ks; + + ks = kp_percpu_data(KTAP_PERCPU_DATA_STATE); + ks->stack = kp_percpu_data(KTAP_PERCPU_DATA_STACK); + G(ks) = G(mainthread); + ks->gclist = NULL; + kp_init_state(ks); + return ks; +} + +/* + * wait ktapio thread read all content in ring buffer. + * + * Here we use stupid approach to sync with ktapio thread, + * note that we cannot use semaphore/completion/other sync method, + * because ktapio thread could be killed by SIG_KILL in anytime, there + * have no safe way to up semaphore or wake waitqueue before thread exit. + * + * we also cannot use waitqueue of current->signal->wait_chldexit to sync + * exit, becasue mainthread and ktapio thread are in same thread group. + * + * Also ktap mainthread must wait ktapio thread exit, otherwise ktapio + * thread will oops when access ktap structure. + */ +static void wait_user_completion(ktap_state *ks) +{ + struct task_struct *tsk = G(ks)->task; + G(ks)->wait_user = 1; + + while (1) { + set_current_state(TASK_INTERRUPTIBLE); + /* sleep for 100 msecs, and try again. */ + schedule_timeout(HZ / 10); + + if (get_nr_threads(tsk) == 1) + break; + } +} + +/* kp_wait: used for mainthread waiting for exit */ +static void kp_wait(ktap_state *ks) +{ + struct task_struct *task = G(ks)->trace_task; + + if (G(ks)->exit) + return; + + ks->stop = 0; + + /* tell workload process to start executing */ + if (G(ks)->parm->workload) + send_sig(SIGINT, G(ks)->trace_task, 0); + + while (!ks->stop) { + set_current_state(TASK_INTERRUPTIBLE); + /* sleep for 100 msecs, and try again. */ + schedule_timeout(HZ / 10); + + if (signal_pending(current)) { + flush_signals(current); + + /* newline for handle CTRL+C display as ^C */ + kp_puts(ks, "\n"); + break; + } + + /* stop waiting if target pid is exited */ + if (task && task->state == TASK_DEAD) + break; + } + +} + +void kp_exit(ktap_state *ks) +{ + set_next_as_exit(ks); + + G(ks)->mainthread->stop = 1; + G(ks)->exit = 1; +} + +void kp_final_exit(ktap_state *ks) +{ + if (!list_empty(&G(ks)->probe_events_head) || + !list_empty(&G(ks)->timers)) + kp_wait(ks); + + if (G(ks)->trace_task) + put_task_struct(G(ks)->trace_task); + + kp_exit_timers(ks); + kp_probe_exit(ks); + + /* free all resources got by ktap */ + kp_tstring_freeall(ks); + kp_free_all_gcobject(ks); + cfunction_cache_exit(ks); + + wait_user_completion(ks); + + kp_transport_exit(ks); + + kp_exitthread(ks); + kp_free(ks, ks->stack); + free_all_ci(ks); + + free_kp_percpu_data(); + + free_cpumask_var(G(ks)->cpumask); + kp_free(ks, ks); +} + +/* ktap mainthread initization, main entry for ktap */ +ktap_state *kp_newstate(ktap_parm *parm, struct dentry *dir) +{ + ktap_state *ks; + pid_t pid; + int cpu; + + ks = kzalloc(sizeof(ktap_state) + sizeof(ktap_global_state), + GFP_KERNEL); + if (!ks) + return NULL; + + ks->stack = kp_malloc(ks, KTAP_STACK_SIZE); + G(ks) = (ktap_global_state *)(ks + 1); + G(ks)->mainthread = ks; + G(ks)->seed = 201236; /* todo: make more random in future */ + G(ks)->task = current; + G(ks)->parm = parm; + INIT_LIST_HEAD(&(G(ks)->timers)); + INIT_LIST_HEAD(&(G(ks)->probe_events_head)); + G(ks)->exit = 0; + + if (kp_transport_init(ks, dir)) + goto out; + + pid = (pid_t)parm->trace_pid; + if (pid != -1) { + struct task_struct *task; + + rcu_read_lock(); + task = pid_task(find_vpid(pid), PIDTYPE_PID); + if (!task) { + kp_error(ks, "cannot find pid %d\n", pid); + rcu_read_unlock(); + goto out; + } + G(ks)->trace_task = task; + get_task_struct(task); + rcu_read_unlock(); + } + + if( !alloc_cpumask_var(&G(ks)->cpumask, GFP_KERNEL)) + goto out; + + cpumask_copy(G(ks)->cpumask, cpu_online_mask); + + cpu = parm->trace_cpu; + if (cpu != -1) { + if (!cpu_online(cpu)) { + printk(KERN_INFO "ktap: cpu %d is not online\n", cpu); + goto out; + } + + cpumask_clear(G(ks)->cpumask); + cpumask_set_cpu(cpu, G(ks)->cpumask); + } + + if (cfunction_cache_init(ks)) + goto out; + + kp_tstring_resize(ks, 512); /* set inital string hashtable size */ + + kp_init_state(ks); + kp_init_registry(ks); + kp_init_arguments(ks, parm->argc, parm->argv); + + /* init library */ + kp_init_baselib(ks); + kp_init_kdebuglib(ks); + kp_init_timerlib(ks); + kp_init_ansilib(ks); + + if (alloc_kp_percpu_data()) + goto out; + + if (kp_probe_init(ks)) + goto out; + + return ks; + + out: + G(ks)->exit = 1; + kp_final_exit(ks); + return NULL; +} + diff --git a/drivers/staging/ktap/scripts/basic/backtrace.kp b/drivers/staging/ktap/scripts/basic/backtrace.kp new file mode 100644 index 000000000000..39b8c3989e7f --- /dev/null +++ b/drivers/staging/ktap/scripts/basic/backtrace.kp @@ -0,0 +1,6 @@ +#!/usr/bin/env ktap + +trace sched:sched_switch { + print_backtrace() +} + diff --git a/drivers/staging/ktap/scripts/basic/event_trigger.kp b/drivers/staging/ktap/scripts/basic/event_trigger.kp new file mode 100644 index 000000000000..3cc8b04f361e --- /dev/null +++ b/drivers/staging/ktap/scripts/basic/event_trigger.kp @@ -0,0 +1,24 @@ +#!/usr/bin/env ktap + +soft_disabled = 1 +this_cpu = 0 + +trace syscalls:sys_enter_open { + print(argevent) + soft_disabled = 0 + this_cpu = cpu() +} + +trace *:* { + if (soft_disabled == 0 && cpu() == this_cpu) { + print(argevent) + } +} + +trace syscalls:sys_exit_open { + print(argevent) + if (cpu() == this_cpu) { + exit() + } +} + diff --git a/drivers/staging/ktap/scripts/basic/event_trigger_ftrace.kp b/drivers/staging/ktap/scripts/basic/event_trigger_ftrace.kp new file mode 100644 index 000000000000..7e0d7d315619 --- /dev/null +++ b/drivers/staging/ktap/scripts/basic/event_trigger_ftrace.kp @@ -0,0 +1,28 @@ +#!/usr/bin/env ktap + + +#This ktap script will output all function calling between +#sys_enter_open and sys_exit_open, in one cpu. + +soft_disabled = 1 +this_cpu = 0 + +trace syscalls:sys_enter_open { + print(argevent) + soft_disabled = 0 + this_cpu = cpu() +} + +trace ftrace:function { + if (soft_disabled == 0 && cpu() == this_cpu) { + print(argevent) + } +} + +trace syscalls:sys_exit_open { + print(argevent) + if (cpu() == this_cpu) { + exit() + } +} + diff --git a/drivers/staging/ktap/scripts/basic/ftrace.kp b/drivers/staging/ktap/scripts/basic/ftrace.kp new file mode 100644 index 000000000000..9feca2ba2318 --- /dev/null +++ b/drivers/staging/ktap/scripts/basic/ftrace.kp @@ -0,0 +1,6 @@ +#!/usr/bin/env ktap + +trace ftrace:function /ip==mutex*/ { + print(cpu(), pid(), execname(), argevent) +} + diff --git a/drivers/staging/ktap/scripts/basic/function_time.kp b/drivers/staging/ktap/scripts/basic/function_time.kp new file mode 100644 index 000000000000..e7859a396d95 --- /dev/null +++ b/drivers/staging/ktap/scripts/basic/function_time.kp @@ -0,0 +1,57 @@ +#!/usr/bin/env ktap + +#Demo for thread-local variable +# +#Note this kind of function time tracing already handled concurrent issue, +#but not aware on the recursion problem, user need to aware this limitation, +#so don't use this script to trace function which could be called recursive. + +self = {} +count_max = 0 +count_min = 0 +count_num = 0 +total_time = 0 + +printf("measure time(us) of function vfs_read\n"); + +trace probe:vfs_read { + if (execname() == "ktap") { + return + } + + self[tid()] = gettimeofday_us() +} + +trace probe:vfs_read%return { + if (execname() == "ktap") { + return + } + + if (self[tid()] == nil) { + return + } + + local durtion = gettimeofday_us() - self[tid()] + if (durtion > count_max) { + count_max = durtion + } + local min = count_min + if (min == 0 || durtion < min) { + count_min = durtion + } + + count_num = count_num + 1 + total_time = total_time + durtion + + self[tid()] = nil +} + +trace_end { + printf("avg\tmax\tmin\n"); + printf("-------------------\n") + + printf("%d\t%d\t%d\n", total_time/count_num, + count_max, count_min) +} + + diff --git a/drivers/staging/ktap/scripts/basic/kretprobe.kp b/drivers/staging/ktap/scripts/basic/kretprobe.kp new file mode 100644 index 000000000000..82de3cffa276 --- /dev/null +++ b/drivers/staging/ktap/scripts/basic/kretprobe.kp @@ -0,0 +1,6 @@ +#!/usr/bin/env ktap + +trace probe:vfs_read%return fd=$retval { + print(execname(), argevent); +} + diff --git a/drivers/staging/ktap/scripts/game/tetris.kp b/drivers/staging/ktap/scripts/game/tetris.kp new file mode 100644 index 000000000000..7125cd4e46ce --- /dev/null +++ b/drivers/staging/ktap/scripts/game/tetris.kp @@ -0,0 +1,328 @@ +#!/usr/bin/env ktap + +# +# Tetris KTAP Script +# +# Copyright (C) 2013/OCT/05 Tadaki SAKAI +# +# based on stapgames (Systemtap Game Collection) +# https://github.com/mhiramat/stapgames/blob/master/games/tetris.stp +# +# - Requirements +# Kernel Configuration: CONFIG_KPROBE_EVENT=y +# CONFIG_EVENT_TRACING=y +# CONFIG_PERF_EVENTS=y +# CONFIG_DEBUG_FS=y +# CPU Architecture : x86_64 +# +# - Setup +# $ sudo mount -t debugfs none /sys/kernel/debug/ +# +# $ git clone https://github.com/ktap/ktap +# $ cd ktap +# $ make 2>&1 | tee ../make.log +# $ sudo make load +# $ sudo sh -c 'echo 50000 > /sys/module/ktapvm/parameters/max_exec_count' +# +# - Run Tetris +# $ sudo ./ktap scripts/game/tetris.kp +# + + +# +# utils +# + +function rand(max) { + r = gettimeofday_us() + if (r < 0) { + r = r * -1 + } + return r % max +} + +color_table = {} +color_table["Black"] = 40 +color_table["Red"] = 41 +color_table["Green"] = 42 +color_table["Yellow"] = 43 +color_table["Blue"] = 44 +color_table["Purple"] = 45 +color_table["Cyan"] = 46 +color_table["White"] = 47 + +function get_color_number(txt) { + return color_table[txt] +} + +color_table_text = {} +color_table_text[40] = "Black" +color_table_text[41] = "Red" +color_table_text[42] = "Green" +color_table_text[43] = "Yellow" +color_table_text[44] = "Blue" +color_table_text[45] = "Purple" +color_table_text[46] = "Cyan" +color_table_text[47] = "White" + +function get_color_text(num) { + return color_table_text[num] +} + +function update_display() { + for (i = 0, 239, 1) { + if ((i % 12 - 11) != 0) { + tmp = "" + } else { + tmp = "\n" + } + + if (display_buffer[240 + i] == back_text) { + printf("%s%s", back_text, tmp) + } else { + ctext = display_buffer[240 + i] + ansi.set_color2(get_color_number(ctext), + get_color_number(ctext)) + printf(" %s", tmp) + ansi.reset_color() + } + + # clear the display buffer + display_buffer[240 + i] = display_buffer[i] + } + + printf("%d\n",point) +} + + +# +# global value +# + +key_code = 0 +point = 0 +block_number = 0 +height = 0 +height_update = 0 + +destination_position = {} +back_text = {} +block_color = {} +display_buffer = {} + +block_data0 = {} +block_data1 = {} +block_data2 = {} +block_data3 = {} +block_data4 = {} +block_data5 = {} +block_data6 = {} +block_table = {} + +# +# Initialize +# + +# Create blocks +# block is represented by the position from the center. +# Every block has "L" part in the center except for a bar. +block_data0[0] = -11 # non-"L" part for each block +block_data1[0] = -24 +block_data2[0] = 2 +block_data3[0] = 13 +block_data4[0] = -13 +block_data5[0] = -1 +block_data6[0] = 2 + +block_table[0] = block_data0 +block_table[1] = block_data1 +block_table[2] = block_data2 +block_table[3] = block_data3 +block_table[4] = block_data4 +block_table[5] = block_data5 +block_table[6] = block_data6 + +for (i = 0, len(block_table) - 1, 1) { + # common "L" part + block_table[i][1] = 0 + block_table[i][2] = 1 + block_table[i][3] = -12 +} + +block_table[6][3] = -1 # bar is not common +# Position: 1 row has 12 columns, +# and (x, y) is represented by h = x + y * 12.p +height = 17 # First block position (center) + +for (i = 0, 240, 1) { + # Wall and Floor (sentinel) + if (((i % 12) < 2) || (i > 228)) { + block_color = "White" + tmp = block_color + } else { + back_text = " " + tmp = back_text + } + display_buffer[i - 1] = tmp + display_buffer[240 + i - 1] = tmp +} + +block_number = rand(len(color_table) - 1) +block_color = get_color_text(block_number + 40) + +ansi.clear_screen() + + +# +# Key Input +# + +trace probe:kbd_event handle=%di event_type=%si event_code=%dx value=%cx { + # Only can run it in x86_64 + # + # Register follow x86_64 call conversion: + # + # x86_64: + # %rcx 4 argument + # %rdx 3 argument + # %rsi 2 argument + # %rdi 1 argument + + local event_code = arg4 + local value = arg5 + + if (value != 0) { + if ((event_code - 4) != 0) { + key_code = event_code + } + } +} + + +# +# timer +# + +tick-200ms { + ansi.clear_screen() + + f = 0 # move/rotate flag + + if (key_code != 0) { # if key is pressed + if(key_code != 103) { #move left or right + # d: movement direction + if ((key_code - 105) != 0) { + if ((key_code - 106) != 0) { + d = 0 + } else { + d = 1 + } + } else { + d = -1 + } + + for (i = 0, 3, 1) { # check if the block can be moved + # destination is free + if (display_buffer[height + + block_table[block_number][i] + d] + != back_text) { + f = 1 + } + } + # move if destinations of every block are free + if (f == 0) { + height = height + d + } + } else { # rotate + for (i = 0, 3, 1) { # check if block can be rotated + # each block position + p = block_table[block_number][i] + + # destination x pos(p/12 rounded) + v = (p * 2 + 252) / 24 - 10 + w = p - v * 12 # destination y pos + + # destination position + destination_position[i] = w * 12 - v + + # check if desetination is free + if (display_buffer[height + + destination_position[i]] != back_text) { + f = 1 + } + } + + if (f == 0) { + # rotate if destinations of every block + # are free + for (i = 0, 3, 1) { + block_table[block_number][i] = + destination_position[i] + } + } + } + } + key_code = 0 # clear the input key + + f = 0 + for (i = 0, 3, 1) { # drop 1 row + # check if destination is free + p = height + block_table[block_number][i] + if (display_buffer[12 + p] != back_text) { + f = 1 + } + + # copy the moving block to display buffer + display_buffer[240 + p] = block_color + } + + if ((f == 1) && (height == 17)) { + update_display() + exit() # exit if there are block at initial position + } + + height_update = !height_update + if (height_update != 0) { + if(f != 0) { # the block can't drop anymore + for (i = 0, 3, 1) { + # fix the block + display_buffer[height + + block_table[block_number][i]] = block_color + } + # determin the next block + block_number = rand(len(color_table) - 1) + + block_color = get_color_text(block_number + 40) + + height = 17 # make the block to initial position + } else { + height = height + 12 # drop the block 1 row + } + } + + k = 1 + for (i = 18, 0, -1) { #check if line is filled + # search for filled line + j = 10 + while ((j > 0) && + (display_buffer[i * 12 + j] != back_text)) { + j = j - 1 + } + + if (j == 0) { # filled! + # add a point: 1 line - 1 point, ..., tetris - 10points + point = point + k + k = k + 1 + + # drop every upper block + j = (i + 1) * 12 + i = i + 1 + while (j > 2 * 12) { + j = j - 1 + display_buffer[j] = display_buffer[j - 12] + } + } + } + + update_display() +} diff --git a/drivers/staging/ktap/scripts/helloworld.kp b/drivers/staging/ktap/scripts/helloworld.kp new file mode 100644 index 000000000000..5673c15b0226 --- /dev/null +++ b/drivers/staging/ktap/scripts/helloworld.kp @@ -0,0 +1,3 @@ +#!/usr/bin/env ktap + +print("Hello World! I am ktap") diff --git a/drivers/staging/ktap/scripts/interrupt/hardirq_time.kp b/drivers/staging/ktap/scripts/interrupt/hardirq_time.kp new file mode 100644 index 000000000000..1d3c33e859d1 --- /dev/null +++ b/drivers/staging/ktap/scripts/interrupt/hardirq_time.kp @@ -0,0 +1,25 @@ +#!/usr/bin/env ktap + +#this script output each average consumimg time of each hardirq +s = aggr_table() +map = {} + +trace irq:irq_handler_entry { + map[cpu()] = gettimeofday_us() +} + +trace irq:irq_handler_exit { + local entry_time = map[cpu()] + if (entry_time == nil) { + return; + } + + s[arg1] = avg(gettimeofday_us() - entry_time) + map[cpu()] = nil +} + +trace_end { + print("hardirq average executing time (us)") + histogram(s) +} + diff --git a/drivers/staging/ktap/scripts/interrupt/softirq_time.kp b/drivers/staging/ktap/scripts/interrupt/softirq_time.kp new file mode 100644 index 000000000000..7e1a9d8a9172 --- /dev/null +++ b/drivers/staging/ktap/scripts/interrupt/softirq_time.kp @@ -0,0 +1,25 @@ +#!/usr/bin/env ktap + +#this script output each average consumimg time of each softirq line +s = aggr_table() +map = {} + +trace irq:softirq_entry { + map[cpu()] = gettimeofday_us() +} + +trace irq:softirq_exit { + local entry_time = map[cpu()] + if (entry_time == nil) { + return; + } + + s[arg1] = avg(gettimeofday_us() - entry_time) + map[cpu()] = nil +} + +trace_end { + print("softirq average executing time (us)") + histogram(s) +} + diff --git a/drivers/staging/ktap/scripts/io/kprobes-do-sys-open.kp b/drivers/staging/ktap/scripts/io/kprobes-do-sys-open.kp new file mode 100644 index 000000000000..a15f911a1804 --- /dev/null +++ b/drivers/staging/ktap/scripts/io/kprobes-do-sys-open.kp @@ -0,0 +1,20 @@ +#!/usr/bin/env ktap + +#Only can run it in x86_64 +# +#Register follow x86_64 call conversion: +# +#x86_64: +# %rcx 4 argument +# %rdx 3 argument +# %rsi 2 argument +# %rdi 1 argument + +trace probe:do_sys_open dfd=%di filename=%si flags=%dx mode=%cx { + printf("[do_sys_open entry]: (%s) open file (%s)\n", + execname(), user_string(arg3)) +} + +trace probe:do_sys_open%return fd=$retval { + printf("[do_sys_open exit]: return fd (%d)\n", arg3) +} diff --git a/drivers/staging/ktap/scripts/io/traceio.kp b/drivers/staging/ktap/scripts/io/traceio.kp new file mode 100644 index 000000000000..8a95a2534384 --- /dev/null +++ b/drivers/staging/ktap/scripts/io/traceio.kp @@ -0,0 +1,56 @@ +#! /usr/bin/env ktap + +# Based on systemtap traceio.stp + +#this script is broken, fix it soon. + +reads = aggr_table() +writes = aggr_table() +total_io = aggr_table() + +trace syscalls:sys_exit_read { + reads[execname()] = sum(arg2) + total_io[execname()] = sum(arg2) +} + +trace syscalls:sys_exit_write { + writes[execname()] = sum(arg2) + total_io[execname()] = sum(arg2) +} + +function humanread_digit(bytes) { + if (bytes > 1024*1024*1024) { + return bytes/1024/1024/1024 + } elseif (bytes > 1024*1024) { + return bytes/1024/1024 + } elseif (bytes > 1024) { + return bytes/1024 + } else { + return bytes + } +} + +function humanread_x(bytes) { + if (bytes > 1024*1024*1024) { + return " GiB" + } elseif (bytes > 1024*1024) { + return " MiB" + } elseif (bytes > 1024) { + return " KiB" + } else { + return " B" + } +} + +tick-1s { + ansi.clear_screen() + for (exec, count in pairs(total_io)) { + local readnum = reads[exec] + local writenum = writes[exec] + printf("%15s r: %12d%s w: %12d%s\n", exec, + humanread_digit(readnum), humanread_x(readnum), + humanread_digit(writenum), humanread_x(writenum)) + } + printf("\n") +} + diff --git a/drivers/staging/ktap/scripts/mem/kmalloc-top.kp b/drivers/staging/ktap/scripts/mem/kmalloc-top.kp new file mode 100644 index 000000000000..f18ed9fc64cf --- /dev/null +++ b/drivers/staging/ktap/scripts/mem/kmalloc-top.kp @@ -0,0 +1,17 @@ +#!/usr/bin/env ktap + +kmalloc_stack = {} + +trace kmem:kmalloc { + kmalloc_stack[backtrace()] += 1 +} + +tick-60s { + for (k, v in pairs(kmalloc_stack)) { + print(k) + printf("%d\n\n", v) + } + + exit() +} + diff --git a/drivers/staging/ktap/scripts/mem/kmem.kp b/drivers/staging/ktap/scripts/mem/kmem.kp new file mode 100644 index 000000000000..c6f2c99c54cd --- /dev/null +++ b/drivers/staging/ktap/scripts/mem/kmem.kp @@ -0,0 +1,30 @@ +#!/usr/bin/env ktap + +count1 = 0 +trace kmem:kmalloc { + count1 = count1 + 1 +} + +count2 = 0 +trace kmem:kfree { + count2 = count2 + 1 +} + +count3 = 0 +trace kmem:mm_page_alloc { + count3 = count3 + 1 +} + +count4 = 0 +trace kmem:mm_page_free { + count4 = count4 + 1 +} + +trace_end { + print("\n") + print("kmem:kmalloc:\t", count1) + print("kmem:kfree:\t", count2) + print("kmem:mm_page_alloc:", count3) + print("kmem:mm_page_free:", count4) + print("trace ending\n") +} diff --git a/drivers/staging/ktap/scripts/profiling/function_profiler.kp b/drivers/staging/ktap/scripts/profiling/function_profiler.kp new file mode 100644 index 000000000000..589017fe83b4 --- /dev/null +++ b/drivers/staging/ktap/scripts/profiling/function_profiler.kp @@ -0,0 +1,41 @@ +#!/usr/bin/env ktap + +#kernel function profile +#You can use this script to know what function is called frequently, +#without enable CONFIG_FUNCTION_PROFILER in kernel. + +s = aggr_table() + +trace ftrace:function { + s[arg1] = count() +} + +trace_end { + histogram(s) +} + +#sample output +#^C +# value ------------- Distribution ------------- count +# sub_preempt_count | @@@@@ 34904 +# add_preempt_count | @@@@@ 33435 +# nsecs_to_jiffies64 | @@@ 19919 +# irqtime_account_process_tick... | @ 9970 +# account_idle_time | @ 9880 +# _raw_spin_lock | 5100 +# _raw_spin_unlock | 5021 +# _raw_spin_unlock_irqrestore | 4235 +# _raw_spin_lock_irqsave | 4232 +# __rcu_read_lock | 3373 +# __rcu_read_unlock | 3373 +# lookup_address | 2392 +# pfn_range_is_mapped | 2384 +# update_cfs_rq_blocked_load | 1983 +# idle_cpu | 1808 +# ktime_get | 1394 +# _raw_spin_unlock_irq | 1270 +# _raw_spin_lock_irq | 1091 +# update_curr | 950 +# irqtime_account_irq | 950 +# ... | +# diff --git a/drivers/staging/ktap/scripts/profiling/stack_profile.kp b/drivers/staging/ktap/scripts/profiling/stack_profile.kp new file mode 100644 index 000000000000..97945602fcea --- /dev/null +++ b/drivers/staging/ktap/scripts/profiling/stack_profile.kp @@ -0,0 +1,26 @@ +#!/usr/bin/env ktap + +# This ktap script samples stacktrace of system per 10us, +# you can use generated output to make a flame graph. +# +# Flame Graphs: +# http://dtrace.org/blogs/brendan/2012/03/17/linux-kernel-performance-flame-graphs/ + +s = aggr_table() + +profile-10us { + s[backtrace()] = count() +} + +tick-60s { + exit() +} + +trace_end { + for (k, v in pairs(s)) { + print(k) + print(v) + print() + } +} + diff --git a/drivers/staging/ktap/scripts/schedule/sched_transition.kp b/drivers/staging/ktap/scripts/schedule/sched_transition.kp new file mode 100644 index 000000000000..eb54b09c3248 --- /dev/null +++ b/drivers/staging/ktap/scripts/schedule/sched_transition.kp @@ -0,0 +1,5 @@ +#!/usr/bin/env ktap + +trace sched:sched_switch { + printf("%s ... ", arg1) +} diff --git a/drivers/staging/ktap/scripts/schedule/schedtimes.kp b/drivers/staging/ktap/scripts/schedule/schedtimes.kp new file mode 100644 index 000000000000..acef904a2cae --- /dev/null +++ b/drivers/staging/ktap/scripts/schedule/schedtimes.kp @@ -0,0 +1,125 @@ +#!/usr/vin/env ktap + +#schedtimer.kp +#Initially inspired by Systemtap schedtimes.stp +#and more bugfree compare with Systemtap's version +# +#Note that the time value is associate with pid, not with execname strictly, +#sometime you will found there have sleep time for command "ls", the reason +#is that sleep time is belong to parent process bash, so clear on this. + +RUNNING = 0 +QUEUED = 1 +SLEEPING = 2 +DEAD = 64 + +run_time = {} +queued_time = {} +sleep_time = {} +io_wait_time = {} + +pid_state = {} +pid_names = {} +prev_timestamp = {} +io_wait = {} + +trace sched:sched_switch { + local prev_comm = arg1 + local prev_pid = arg2 + local prev_state = arg4 + local next_comm = arg5 + local next_pid = arg6 + local t = gettimeofday_us() + + if (pid_state[prev_pid] == nil) { + #do nothing + } elseif (pid_state[prev_pid] == RUNNING) { + run_time[prev_pid] += t - prev_timestamp[prev_pid] + } elseif (pid_state[prev_pid] == QUEUED) { + #found this: + #sched_wakeup comm=foo + #sched_switch prev_comm=foo + run_time[prev_pid] += t - prev_timestamp[prev_pid] + } + + pid_names[prev_pid] = prev_comm + prev_timestamp[prev_pid] = t + + if (prev_state == DEAD) { + pid_state[prev_pid] = DEAD + } elseif (prev_state > 0) { + if (in_iowait() == 1) { + io_wait[prev_pid] = 1 + } + pid_state[prev_pid] = SLEEPING + } elseif (prev_state == 0) { + pid_state[prev_pid] = QUEUED + } + + if (pid_state[next_pid] == nil) { + pid_state[next_pid] = RUNNING + } elseif (pid_state[next_pid] == QUEUED) { + queued_time[next_pid] += t - prev_timestamp[next_pid] + pid_state[next_pid] = RUNNING + } + + pid_names[next_pid] = next_comm + prev_timestamp[next_pid] = t +} + +trace sched:sched_wakeup, sched:sched_wakeup_new { + local comm = arg1 + local wakeup_pid = arg2 + local success = arg4 + local t = gettimeofday_us() + + if (pid_state[wakeup_pid] == nil) { + #do nothing + } elseif (pid_state[wakeup_pid] == SLEEPING) { + local durtion = t - prev_timestamp[wakeup_pid] + + sleep_time[wakeup_pid] += durtion + if (io_wait[wakeup_pid] == 1) { + io_wait_time[wakeup_pid] += durtion + io_wait[wakeup_pid] = 0 + } + } elseif (pid_state[wakeup_pid] == RUNNING) { + return + } + + pid_names[wakeup_pid] = comm + prev_timestamp[wakeup_pid] = t + pid_state[wakeup_pid] = QUEUED +} + +trace_end { + local t = gettimeofday_us() + + for (pid, state in pairs(pid_state)) { + local durtion = t - prev_timestamp[pid] + if (state == SLEEPING) { + sleep_time[pid] += durtion + } elseif (state == QUEUED) { + queued_time[pid] += durtion + } elseif (state == RUNNING) { + run_time[pid] += durtion + } + } + + printf ("%16s: %6s %10s %10s %10s %10s %10s\n\n", + "execname", "pid", "run(us)", "sleep(us)", "io_wait(us)", + "queued(us)", "total(us)") + + for (pid, time in pairs(run_time)) { + if (sleep_time[pid] == nil) { + sleep_time[pid] = 0 + } + if (queued_time[pid] == nil) { + queue_time[pid] = 0 + } + printf("%16s: %6d %10d %10d %10d %10d %10d\n", + pid_names[pid], pid, run_time[pid], sleep_time[pid], + io_wait_time[pid], queued_time[pid], + run_time[pid] + sleep_time[pid] + queued_time[pid]); + } +} diff --git a/drivers/staging/ktap/scripts/syscalls/errinfo.kp b/drivers/staging/ktap/scripts/syscalls/errinfo.kp new file mode 100644 index 000000000000..049031b3e374 --- /dev/null +++ b/drivers/staging/ktap/scripts/syscalls/errinfo.kp @@ -0,0 +1,145 @@ +#!/usr/bin/env ktap + +#errdesc get from include/uapi/asm-generic/errno*.h +errdesc = { + [1] = "Operation not permitted", #EPERM + [2] = "No such file or directory", #ENOENT + [3] = "No such process", #ESRCH + [4] = "Interrupted system call", #EINRT + [5] = "I/O error", #EIO + [6] = "No such device or address", #ENXIO + [7] = "Argument list too long", #E2BIG + [8] = "Exec format error", #ENOEXEC + [9] = "Bad file number", #EBADF + [10] = "No child processes", #ECHILD + [11] = "Try again", #EAGAIN + [12] = "Out of memory", #ENOMEM + [13] = "Permission denied", #EACCES + [14] = "Bad address", #EFAULT + [15] = "Block device required", #ENOTBLK + [16] = "Device or resource busy", #EBUSY + [17] = "File exists", #EEXIST + [18] = "Cross-device link", #EXDEV + [19] = "No such device", #ENODEV + [20] = "Not a directory", #ENOTDIR + [21] = "Is a directory", #EISDIR + [22] = "Invalid argument", #EINVAL + [23] = "File table overflow", #ENFILE + [24] = "Too many open files", #EMFILE + [25] = "Not a typewriter", #ENOTTY + [26] = "Text file busy", #ETXTBSY + [27] = "File too large", #EFBIG + [28] = "No space left on device", #ENOSPC + [29] = "Illegal seek", #ESPIPE + [30] = "Read-only file system", #EROFS + [31] = "Too many links", #EMLINK + [32] = "Broken pipe", #EPIPE + [33] = "Math argument out of domain of func", #EDOM + [34] = "Math result not representable", #ERANGE + + [35] = "Resource deadlock would occur", #EDEADLK + [36] = "File name too long", #ENAMETOOLONG + [37] = "No record locks available", #ENOLCK + [38] = "Function not implemented", #ENOSYS + [39] = "Directory not empty", #ENOTEMPTY + [40] = "Too many symbolic links encountered", #ELOOP + [42] = "No message of desired type", #ENOMSG + [43] = "Identifier removed", #EIDRM + [44] = "Channel number out of range", #ECHRNG + [45] = "Level 2 not synchronized", #EL2NSYNC + [46] = "Level 3 halted", #EL3HLT + [47] = "Level 3 reset", #EL3RST + [48] = "Link number out of range", #ELNRNG + [49] = "Protocol driver not attached", #EUNATCH + [50] = "No CSI structure available", #ENOCSI + [51] = "Level 2 halted", #EL2HLT + [52] = "Invalid exchange", #EBADE + [53] = "Invalid request descriptor", #EBADR + [54] = "Exchange full", #EXFULL + [55] = "No anode", #ENOANO + [56] = "Invalid request code", #EBADRQC + [57] = "Invalid slot", #EBADSLT + + [59] = "Bad font file format", #EBFONT + [60] = "Device not a stream", #ENOSTR + [61] = "No data available", #ENODATA + [62] = "Timer expired", #ETIME + [63] = "Out of streams resources", #ENOSR + [64] = "Machine is not on the network", #ENONET + [65] = "Package not installed", #ENOPKG + [66] = "Object is remote", #EREMOTE + [67] = "Link has been severed", #ENOLINK + [68] = "Advertise error", #EADV + [69] = "Srmount error", #ESRMNT + [70] = "Communication error on send", #ECOMM + [71] = "Protocol error", #EPROTO + [72] = "Multihop attempted", #EMULTIHOP + [73] = "RFS specific error", #EDOTDOT + [74] = "Not a data message", #EBADMSG + [75] = "Value too large for defined data type", #EOVERFLOW + [76] = "Name not unique on network", #ENOTUNIQ + [77] = "File descriptor in bad state", #EBADFD + [78] = "Remote address changed", #EREMCHG + [79] = "Can not access a needed shared library", #ELIBACC + [80] = "Accessing a corrupted shared library", #ELIBBAD + [81] = ".lib section in a.out corrupted", #ELIBSCN + [82] = "Attempting to link in too many shared libraries", #ELIBMAX + [83] = "Cannot exec a shared library directly", #ELIBEXEC + [84] = "Illegal byte sequence", #EILSEQ + [85] = "Interrupted system call should be restarted", #ERESTART + [86] = "Streams pipe error", #ESTRPIPE + [87] = "Too many users", #EUSERS + [88] = "Socket operation on non-socket", #ENOTSOCK + [89] = "Destination address required", #EDESTADDRREQ + [90] = "Message too long", #EMSGSIZE + [91] = "Protocol wrong type for socket", #EPROTOTYPE + [92] = "Protocol not available", #ENOPROTOOPT + [93] = "Protocol not supported", #EPROTONOSUPPORT + [94] = "Socket type not supported", #ESOCKTNOSUPPORT + [95] = "Operation not supported on transport endpoint", #EOPNOTSUPP + [96] = "Protocol family not supported", #EPFNOSUPPORT + [97] = "Address family not supported by protocol", #EAFNOSUPPORT + [98] = "Address already in use", #EADDRINUSE + [99] = "Cannot assign requested address", #EADDRNOTAVAIL + [100] = "Network is down", #ENETDOWN + [101] = "Network is unreachable", #ENETUNREACH + [102] = "Network dropped connection because of reset", #ENETRESET + [103] = "Software caused connection abort", #ECONNABORTED + [104] = "Connection reset by peer", #ECONNRESET + [105] = "No buffer space available", #ENOBUFS + [106] = "Transport endpoint is already connected", #EISCONN + [107] = "Transport endpoint is not connected", #ENOTCONN + [108] = " Cannot send after transport endpoint shutdown", #ESHUTDOWN + [109] = "Too many references: cannot splice", #ETOOMANYREFS + [110] = "Connection timed out", #ETIMEDOUT + [111] = "Connection refused", #ECONNREFUSED + [112] = "Host is down", #EHOSTDOWN + [113] = "No route to host", #EHOSTUNREACH + [114] = "Operation already in progress", #EALREADY + [115] = "Operation now in progress", #EINPROGRESS + [116] = "Stale NFS file handle", #ESTALE + [117] = "Structure needs cleaning", #EUCLEAN + [118] = "Not a XENIX named type file", #ENOTNAM + [119] = "No XENIX semaphores available", #ENAVAIL + [120] = "Is a named type file", #EISNAM + [121] = "Remote I/O error", #EREMOTEIO + [122] = "Quota exceeded", #EDQUOT + [123] = "No medium found", #ENOMEDIUM + [124] = "Wrong medium type", #EMEDIUMTYPE + [125] = "Operation Canceled", #ECANCELED + [126] = "Required key not available", #ENOKEY + [127] = "Key has expired", #EKEYEXPIRED + [128] = "Key has been revoked", #EKEYREVOKED + [129] = "Key was rejected by service", #EKEYREJECTED + [130] = "Owner died", #EOWNERDEAD + [131] = "State not recoverable", #ENOTRECOVERABLE + +} + +trace syscalls:sys_exit_* { + if (arg2 < 0) { + local errno = -arg2 + printf("%-15s%-20s\t%d\t%-30s\n", + execname(), argname, errno, errdesc[errno]) + } +} diff --git a/drivers/staging/ktap/scripts/syscalls/sctop.kp b/drivers/staging/ktap/scripts/syscalls/sctop.kp new file mode 100644 index 000000000000..6df45cb98178 --- /dev/null +++ b/drivers/staging/ktap/scripts/syscalls/sctop.kp @@ -0,0 +1,14 @@ +#! /usr/bin/env ktap + +#this script is broken, fix it soon. +s = {} + +trace syscalls:sys_enter_* { + s[argname] += 1 +} + +tick-5s { + ansi.clear_screen() + histogram(s) + delete(s) +} diff --git a/drivers/staging/ktap/scripts/syscalls/syscalls.kp b/drivers/staging/ktap/scripts/syscalls/syscalls.kp new file mode 100644 index 000000000000..8bbaacad68ac --- /dev/null +++ b/drivers/staging/ktap/scripts/syscalls/syscalls.kp @@ -0,0 +1,6 @@ +#!/usr/bin/env ktap + +trace syscalls:* { + print(cpu(), pid(), execname(), argevent) +} + diff --git a/drivers/staging/ktap/scripts/syscalls/syscalls_count.kp b/drivers/staging/ktap/scripts/syscalls/syscalls_count.kp new file mode 100644 index 000000000000..363c6226e8a9 --- /dev/null +++ b/drivers/staging/ktap/scripts/syscalls/syscalls_count.kp @@ -0,0 +1,56 @@ +#!/usr/bin/env ktap + +s = aggr_table() + +trace syscalls:sys_enter_* { + s[argname] = count() +} + +trace_end { + histogram(s) +} + +print("Press Control-C to stop.") + +#Result: +# +#[root@jovi ktap]# ./ktap scripts/syscalls_histogram.kp +#^C +# value ------------- Distribution ------------- count +# sys_enter_rt_sigprocmask |@@@@@@ 326 +# sys_enter_read |@@@@@ 287 +# sys_enter_close |@@@@ 236 +# sys_enter_open |@@@@ 222 +# sys_enter_stat64 |@@ 132 +# sys_enter_select |@@ 123 +# sys_enter_rt_sigaction |@@ 107 +# sys_enter_poll |@ 72 +# sys_enter_write |@ 70 +# sys_enter_mmap_pgoff |@ 58 +# sys_enter_fstat64 | 41 +# sys_enter_nanosleep | 23 +# sys_enter_access | 20 +# sys_enter_mprotect | 18 +# sys_enter_geteuid | 17 +# sys_enter_getegid | 16 +# sys_enter_getuid | 16 +# sys_enter_getgid | 16 +# sys_enter_brk | 15 +# sys_enter_waitpid | 11 +# sys_enter_time | 10 +# sys_enter_ioctl | 9 +# sys_enter_munmap | 9 +# sys_enter_fcntl64 | 7 +# sys_enter_dup2 | 7 +# sys_enter_clone | 6 +# sys_enter_exit_group | 6 +# sys_enter_execve | 4 +# sys_enter_pipe | 3 +# sys_enter_gettimeofday | 3 +# sys_enter_getdents | 2 +# sys_enter_getgroups | 2 +# sys_enter_statfs64 | 2 +# sys_enter_lseek | 2 +# sys_enter_openat | 1 +# sys_enter_newuname | 1 + diff --git a/drivers/staging/ktap/scripts/syscalls/syscalls_count_by_proc.kp b/drivers/staging/ktap/scripts/syscalls/syscalls_count_by_proc.kp new file mode 100644 index 000000000000..7b7d722b893d --- /dev/null +++ b/drivers/staging/ktap/scripts/syscalls/syscalls_count_by_proc.kp @@ -0,0 +1,24 @@ +#!/usr/bin/env ktap + +s = aggr_table() + +trace syscalls:sys_enter_* { + s[execname()] = count() +} + +trace_end { + histogram(s) +} + +print("Press Control-C to stop.") + +#Result: +# +#[root@jovi ktap]# ./ktap scripts/syscalls_histogram2.kp +#^C +# value ------------- Distribution ------------- count +# sshd |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 196 +# iscsid |@@@@ 24 +# sendmail |@ 9 + + diff --git a/drivers/staging/ktap/scripts/tracepoints/eventcount.kp b/drivers/staging/ktap/scripts/tracepoints/eventcount.kp new file mode 100644 index 000000000000..9bc357fca56b --- /dev/null +++ b/drivers/staging/ktap/scripts/tracepoints/eventcount.kp @@ -0,0 +1,212 @@ +#!/usr/bin/env ktap + +# showing all tracepoints in histogram style + +s = {} + +trace *:* { + s[argname] += 1 +} + +trace_end { + histogram(s) +} + +print("Press Control-C to stop.") + +#Results: +#^C +# +# value ------------- Distribution ------------- count +# rcu_utilization |@@@@@ 225289 +# cpu_idle |@@@ 120168 +# sched_wakeup |@@ 91950 +# timer_cancel |@@ 91232 +# timer_start |@@ 91201 +# sched_stat_sleep |@@ 90981 +# timer_expire_exit |@@ 90634 +# timer_expire_entry |@@ 90625 +# hrtimer_cancel |@ 75411 +# hrtimer_start |@ 74946 +# softirq_raise |@ 63117 +# softirq_exit |@ 63109 +# softirq_entry |@ 63094 +# sched_switch |@ 62331 +# sched_stat_wait |@ 60491 +# hrtimer_expire_exit |@ 47538 +# hrtimer_expire_entry |@ 47530 +# sched_stat_runtime | 2780 +# kmem_cache_free | 2684 +# kmem_cache_alloc | 2415 +# kfree | 2288 +# sys_exit | 2145 +# sys_enter | 2145 +# sys_exit_rt_sigprocmask | 1000 +# sys_enter_rt_sigprocmask | 1000 +# timer_init | 912 +# sched_stat_blocked | 685 +# kmalloc | 667 +# workqueue_execute_end | 621 +# workqueue_execute_start | 621 +# sys_enter_select | 566 +# sys_exit_select | 566 +# sys_enter_read | 526 +# sys_exit_read | 526 +# mm_page_free | 478 +# mm_page_alloc | 427 +# mm_page_free_batched | 382 +# net_dev_queue | 296 +# net_dev_xmit | 296 +# consume_skb | 296 +# sys_exit_write | 290 +# sys_enter_write | 290 +# kfree_skb | 289 +# kmem_cache_alloc_node | 269 +# kmalloc_node | 263 +# sys_enter_close | 249 +# sys_exit_close | 249 +# hrtimer_init | 248 +# netif_receive_skb | 242 +# sys_enter_open | 237 +# sys_exit_open | 237 +# napi_poll | 226 +# sched_migrate_task | 207 +# sys_exit_poll | 173 +# sys_enter_poll | 173 +# workqueue_queue_work | 152 +# workqueue_activate_work | 152 +# sys_enter_stat64 | 133 +# sys_exit_stat64 | 133 +# sys_exit_rt_sigaction | 133 +# sys_enter_rt_sigaction | 133 +# irq_handler_entry | 125 +# irq_handler_exit | 125 +# mm_page_alloc_zone_locked | 99 +# sys_exit_mmap_pgoff | 66 +# sys_enter_mmap_pgoff | 66 +# sys_exit_fstat64 | 54 +# sys_enter_fstat64 | 54 +# sys_enter_nanosleep | 51 +# sys_exit_nanosleep | 51 +# block_bio_queue | 46 +# block_bio_remap | 46 +# block_bio_complete | 46 +# mix_pool_bytes | 44 +# mm_page_pcpu_drain | 31 +# sys_exit_time | 23 +# sys_enter_time | 23 +# sys_exit_access | 20 +# sys_enter_access | 20 +# mix_pool_bytes_nolock | 18 +# sys_enter_mprotect | 18 +# sys_exit_mprotect | 18 +# sys_enter_geteuid | 17 +# sys_exit_geteuid | 17 +# sys_enter_munmap | 17 +# sys_exit_munmap | 17 +# block_getrq | 16 +# sys_enter_getuid | 16 +# sys_enter_getgid | 16 +# sys_exit_getgid | 16 +# sys_exit_getuid | 16 +# block_rq_issue | 16 +# scsi_dispatch_cmd_start | 16 +# block_rq_complete | 16 +# scsi_dispatch_cmd_done | 16 +# sys_enter_getegid | 16 +# sys_exit_getegid | 16 +# block_rq_insert | 16 +# skb_copy_datagram_iovec | 15 +# sys_enter_brk | 15 +# sys_exit_brk | 15 +# credit_entropy_bits | 14 +# wbc_writepage | 14 +# sys_exit_clone | 12 +# block_touch_buffer | 12 +# sched_process_wait | 11 +# sys_enter_waitpid | 11 +# sys_exit_waitpid | 11 +# writeback_written | 10 +# writeback_start | 10 +# writeback_queue_io | 10 +# ext4_es_lookup_extent_enter | 9 +# sys_enter_ioctl | 9 +# sys_exit_ioctl | 9 +# ext4_ext_map_blocks_enter | 9 +# ext4_ext_map_blocks_exit | 9 +# ext4_es_lookup_extent_exit | 9 +# ext4_es_insert_extent | 9 +# ext4_ext_show_extent | 8 +# extract_entropy | 8 +#ext4_es_find_delayed_extent_exit | 8 +# ext4_es_find_delayed_extent_... | 8 +# writeback_pages_written | 7 +# sys_exit_dup2 | 7 +# sys_enter_dup2 | 7 +# signal_generate | 7 +# sys_enter_fcntl64 | 7 +# sys_exit_fcntl64 | 7 +# global_dirty_state | 7 +# writeback_dirty_inode_start | 7 +# block_bio_backmerge | 7 +# writeback_dirty_inode | 7 +# sched_wakeup_new | 6 +# sched_process_free | 6 +# sys_enter_exit_group | 6 +# task_newtask | 6 +# sys_enter_clone | 6 +# sched_process_fork | 6 +# sched_process_exit | 6 +# sys_exit_gettimeofday | 5 +# signal_deliver | 5 +# sys_enter_gettimeofday | 5 +# writeback_single_inode | 4 +# sys_enter_execve | 4 +# task_rename | 4 +# sched_process_exec | 4 +# block_dirty_buffer | 4 +# sys_exit_execve | 4 +# block_unplug | 4 +# sched_stat_iowait | 4 +# writeback_single_inode_start | 4 +# block_plug | 4 +# writeback_write_inode | 3 +# sys_enter_pipe | 3 +# writeback_dirty_page | 3 +# writeback_write_inode_start | 3 +# ext4_mark_inode_dirty | 3 +# ext4_journal_start | 3 +# sys_exit_pipe | 3 +# jbd2_drop_transaction | 2 +# jbd2_commit_locking | 2 +# jbd2_commit_flushing | 2 +# jbd2_handle_start | 2 +# jbd2_run_stats | 2 +# sys_exit_getdents | 2 +# jbd2_checkpoint_stats | 2 +# sys_enter_getgroups | 2 +# jbd2_start_commit | 2 +# jbd2_end_commit | 2 +# ext4_da_writepages | 2 +# jbd2_handle_stats | 2 +# sys_enter_statfs64 | 2 +# sys_exit_statfs64 | 2 +# sys_exit_getgroups | 2 +# sys_exit_lseek | 2 +# sys_enter_lseek | 2 +# sys_enter_getdents | 2 +# ext4_da_write_pages | 2 +# jbd2_commit_logging | 2 +# ext4_request_blocks | 1 +# sys_exit_openat | 1 +# ext4_discard_preallocations | 1 +# ext4_mballoc_alloc | 1 +# sys_enter_openat | 1 +# ext4_da_writepages_result | 1 +# ext4_allocate_blocks | 1 +# sys_enter_newuname | 1 +# ext4_da_update_reserve_space | 1 +# ext4_get_reserved_cluster_alloc | 1 +# sys_exit_newuname | 1 +# writeback_wake_thread | 1 + diff --git a/drivers/staging/ktap/scripts/tracepoints/eventcount_by_proc.kp b/drivers/staging/ktap/scripts/tracepoints/eventcount_by_proc.kp new file mode 100644 index 000000000000..8706f73d5b40 --- /dev/null +++ b/drivers/staging/ktap/scripts/tracepoints/eventcount_by_proc.kp @@ -0,0 +1,59 @@ +#!/usr/bin/env ktap + +# showing all tracepoints in histogram style + +s = aggr_table() + +trace *:* { + s[execname()] = count() +} + +trace_end { + histogram(s) +} + +print("Press Control-C to stop.") + +#Results: +#^C +# value ------------- Distribution ------------- count +# swapper/0 |@@@@@@@@@@@@ 354378 +# swapper/1 |@@@@@@@@@@ 284984 +# ps |@@@@ 115697 +# ksmtuned |@@@ 95857 +# iscsid |@@ 80008 +# awk |@ 30354 +# irqbalance | 16530 +# rcu_sched | 15892 +# sendmail | 14463 +# kworker/0:1 | 10540 +# kworker/u4:2 | 9250 +# kworker/1:2 | 7943 +# sleep | 7555 +# crond | 3911 +# ksoftirqd/0 | 3817 +# sshd | 2849 +# systemd-journal | 2209 +# migration/1 | 1601 +# migration/0 | 1350 +# dhclient | 1343 +# nm-dhcp-client. | 1208 +# ksoftirqd/1 | 1064 +# watchdog/1 | 966 +# watchdog/0 | 964 +# khugepaged | 776 +# dbus-daemon | 611 +# rpcbind | 607 +# gdbus | 529 +# NetworkManager | 399 +# jbd2/dm-1-8 | 378 +# modem-manager | 184 +# abrt-watch-log | 157 +# polkitd | 156 +# rs:main Q:Reg | 153 +# avahi-daemon | 151 +# rsyslogd | 102 +# systemd | 96 +# kworker/0:1H | 45 +# smartd | 30 + diff --git a/drivers/staging/ktap/scripts/tracepoints/tracepoints.kp b/drivers/staging/ktap/scripts/tracepoints/tracepoints.kp new file mode 100644 index 000000000000..5d088695bbf2 --- /dev/null +++ b/drivers/staging/ktap/scripts/tracepoints/tracepoints.kp @@ -0,0 +1,6 @@ +#!/usr/bin/env ktap + +trace *:* { + print(cpu(), pid(), execname(), argevent) +} + diff --git a/drivers/staging/ktap/scripts/userspace/uprobes-malloc.kp b/drivers/staging/ktap/scripts/userspace/uprobes-malloc.kp new file mode 100644 index 000000000000..14c172f8f327 --- /dev/null +++ b/drivers/staging/ktap/scripts/userspace/uprobes-malloc.kp @@ -0,0 +1,9 @@ +#!/usr/bin/env ktap + +trace probe:/lib/libc.so.6:0x000773c0 { + print("entry:", execname(), argevent) +} + +trace probe:/lib/libc.so.6:0x000773c0%return { + print("exit:", execname(), argevent) +} diff --git a/drivers/staging/ktap/test/aggr_table.kp b/drivers/staging/ktap/test/aggr_table.kp new file mode 100644 index 000000000000..985b634952c1 --- /dev/null +++ b/drivers/staging/ktap/test/aggr_table.kp @@ -0,0 +1,50 @@ +#!/usr/bin/env ktap + +function failed() { + printf("failed\n"); + exit(-1); +} + +#---------------------------------# + +s = aggr_table() + +s["count"] = count() +s["count"] = count() +s["count"] = count() + +s["max"] = max(1) +s["max"] = max(0) +s["max"] = max(100) + +s["min"] = min(50) +s["min"] = min(2) +s["min"] = min(100) + +s["sum"] = sum(10) +s["sum"] = sum(20) +s["sum"] = sum(30) + +s["avg"] = avg(10) +s["avg"] = avg(20) +s["avg"] = avg(30) + +if (s["count"] != 3) { + failed() +} + +if (s["max"] != 100) { + failed() +} + +if (s["min"] != 2) { + failed() +} + +if (s["sum"] != 60) { + failed() +} + +if (s["avg"] != 20) { + failed() +} diff --git a/drivers/staging/ktap/test/ansi.kp b/drivers/staging/ktap/test/ansi.kp new file mode 100644 index 000000000000..e8a27835ce75 --- /dev/null +++ b/drivers/staging/ktap/test/ansi.kp @@ -0,0 +1,20 @@ +#!/usr/bin/env ktap + +ansi.clear_screen() + +ansi.set_color(32) +printf("this line should be Green color\n") + +ansi.set_color(31) +printf("this line should be Red color\n") + +ansi.set_color2(34, 43) +printf("this line should be Blue color, with Yellow background\n") + +ansi.reset_color() +ansi.set_color3(34, 46, 4) +printf("this line should be Blue color, with Cyan background, underline single attribute\n") + +ansi.reset_color() +ansi.new_line() + diff --git a/drivers/staging/ktap/test/arg.kp b/drivers/staging/ktap/test/arg.kp new file mode 100644 index 000000000000..0cf16ebf3227 --- /dev/null +++ b/drivers/staging/ktap/test/arg.kp @@ -0,0 +1,24 @@ +#!/usr/bin/env ktap + +function failed() { + printf("failed\n"); + exit(-1); +} + +#-----------------------------------------# + +if (!arg[0]) { + failed() +} + +if (arg[1] != 1) { + failed() +} + +if (arg[2] != "testing") { + failed() +} + +if (arg[3] != "2 3 4") { + failed() +} diff --git a/drivers/staging/ktap/test/arith.kp b/drivers/staging/ktap/test/arith.kp new file mode 100644 index 000000000000..32880a140c69 --- /dev/null +++ b/drivers/staging/ktap/test/arith.kp @@ -0,0 +1,27 @@ +#!/usr/bin/env ktap + +function failed() { + printf("failed\n"); + exit(-1); +} + +#-----------------------------------------# + +a = 4 +b = 5 + +if ((a + b) != 9) { + failed() +} + +if ((a - b) != -1) { + failed() +} + +if ((a % b) != 4) { + failed() +} + +if ((a / b) != 0) { + failed() +} diff --git a/drivers/staging/ktap/test/bench/sembench.c b/drivers/staging/ktap/test/bench/sembench.c new file mode 100644 index 000000000000..08119341c91d --- /dev/null +++ b/drivers/staging/ktap/test/bench/sembench.c @@ -0,0 +1,556 @@ +/* + * copyright Oracle 2007. Licensed under GPLv2 + * To compile: gcc -Wall -o sembench sembench.c -lpthread + * + * usage: sembench -t thread count -w wakenum -r runtime -o op + * op can be: 0 (ipc sem) 1 (nanosleep) 2 (futexes) + * + * example: + * sembench -t 1024 -w 512 -r 60 -o 2 + * runs 1024 threads, waking up 512 at a time, running for 60 seconds using + * futex locking. + * + */ +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 199309 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VERSION "0.2" + +/* futexes have been around since 2.5.something, but it still seems I + * need to make my own syscall. Sigh. + */ +#define FUTEX_WAIT 0 +#define FUTEX_WAKE 1 +#define FUTEX_FD 2 +#define FUTEX_REQUEUE 3 +#define FUTEX_CMP_REQUEUE 4 +#define FUTEX_WAKE_OP 5 +static inline int futex (int *uaddr, int op, int val, + const struct timespec *timeout, + int *uaddr2, int val3) +{ + return syscall(__NR_futex, uaddr, op, val, timeout, uaddr2, val3); +} + +static void smp_mb(void) +{ + __sync_synchronize(); +} + +static int all_done = 0; +static int timeout_test = 0; + +#define SEMS_PERID 250 + +struct sem_operations; + +struct lockinfo { + unsigned long id; + unsigned long index; + int data; + pthread_t tid; + struct lockinfo *next; + struct sem_operations *ops; + unsigned long ready; +}; + +struct sem_wakeup_info { + int wakeup_count; + struct sembuf sb[SEMS_PERID]; +}; + +struct sem_operations { + void (*wait)(struct lockinfo *l); + int (*wake)(struct sem_wakeup_info *wi, int num_semids, int num); + void (*setup)(struct sem_wakeup_info **wi, int num_semids); + void (*cleanup)(int num_semids); + char *name; +}; + +int *semid_lookup = NULL; + +pthread_mutex_t worklist_mutex = PTHREAD_MUTEX_INITIALIZER; +static unsigned long total_burns = 0; +static unsigned long min_burns = ~0UL; +static unsigned long max_burns = 0; + +/* currently running threads */ +static int thread_count = 0; + +struct lockinfo *worklist = NULL; +static int workers_started = 0; + +/* total threads started */ +static int num_threads = 2048; + +static void worklist_add(struct lockinfo *l) +{ + smp_mb(); + l->ready = 1; +} + +static struct lockinfo *worklist_rm(void) +{ + static int last_index = 0; + int i; + struct lockinfo *l; + + for (i = 0; i < num_threads; i++) { + int test = (last_index + i) % num_threads; + + l = worklist + test; + smp_mb(); + if (l->ready) { + l->ready = 0; + last_index = test; + return l; + } + } + return NULL; +} + +/* ipc semaphore post& wait */ +void wait_ipc_sem(struct lockinfo *l) +{ + struct sembuf sb; + int ret; + struct timespec *tvp = NULL; + struct timespec tv = { 0, 1 }; + + sb.sem_num = l->index; + sb.sem_flg = 0; + + sb.sem_op = -1; + l->data = 1; + + if (timeout_test && (l->id % 5) == 0) + tvp = &tv; + + worklist_add(l); + ret = semtimedop(semid_lookup[l->id], &sb, 1, tvp); + + while(l->data != 0 && tvp) { + struct timespec tv2 = { 0, 500 }; + nanosleep(&tv2, NULL); + } + + if (l->data != 0) { + if (tvp) + return; + fprintf(stderr, "wakeup without data update\n"); + exit(1); + } + if (ret) { + if (errno == EAGAIN && tvp) + return; + perror("semtimed op"); + exit(1); + } +} + +int ipc_wake_some(struct sem_wakeup_info *wi, int num_semids, int num) +{ + int i; + int ret; + struct lockinfo *l; + int found = 0; + + for (i = 0; i < num_semids; i++) { + wi[i].wakeup_count = 0; + } + while(num > 0) { + struct sembuf *sb; + l = worklist_rm(); + if (!l) + break; + if (l->data != 1) + fprintf(stderr, "warning, lockinfo data was %d\n", + l->data); + l->data = 0; + sb = wi[l->id].sb + wi[l->id].wakeup_count; + sb->sem_num = l->index; + sb->sem_op = 1; + sb->sem_flg = IPC_NOWAIT; + wi[l->id].wakeup_count++; + found++; + num--; + } + if (!found) + return 0; + for (i = 0; i < num_semids; i++) { + int wakeup_total; + int cur; + int offset = 0; + if (!wi[i].wakeup_count) + continue; + wakeup_total = wi[i].wakeup_count; + while(wakeup_total > 0) { + cur = wakeup_total > 64 ? 64 : wakeup_total; + ret = semtimedop(semid_lookup[i], wi[i].sb + offset, + cur, NULL); + if (ret) { + perror("semtimedop"); + exit(1); + } + offset += cur; + wakeup_total -= cur; + } + } + return found; +} + +void setup_ipc_sems(struct sem_wakeup_info **wi, int num_semids) +{ + int i; + *wi = malloc(sizeof(**wi) * num_semids); + semid_lookup = malloc(num_semids * sizeof(int)); + for(i = 0; i < num_semids; i++) { + semid_lookup[i] = semget(IPC_PRIVATE, SEMS_PERID, + IPC_CREAT | 0777); + if (semid_lookup[i] < 0) { + perror("semget"); + exit(1); + } + } + sleep(10); +} + +void cleanup_ipc_sems(int num) +{ + int i; + for (i = 0; i < num; i++) { + semctl(semid_lookup[i], 0, IPC_RMID); + } +} + +struct sem_operations ipc_sem_ops = { + .wait = wait_ipc_sem, + .wake = ipc_wake_some, + .setup = setup_ipc_sems, + .cleanup = cleanup_ipc_sems, + .name = "ipc sem operations", +}; + +/* futex post & wait */ +void wait_futex_sem(struct lockinfo *l) +{ + int ret; + l->data = 1; + worklist_add(l); + while(l->data == 1) { + ret = futex(&l->data, FUTEX_WAIT, 1, NULL, NULL, 0); + /* + if (ret && ret != EWOULDBLOCK) { + perror("futex wait"); + exit(1); + }*/ + } +} + +int futex_wake_some(struct sem_wakeup_info *wi, int num_semids, int num) +{ + int i; + int ret; + struct lockinfo *l; + int found = 0; + + for (i = 0; i < num; i++) { + l = worklist_rm(); + if (!l) + break; + if (l->data != 1) + fprintf(stderr, "warning, lockinfo data was %d\n", + l->data); + l->data = 0; + ret = futex(&l->data, FUTEX_WAKE, 1, NULL, NULL, 0); + if (ret < 0) { + perror("futex wake"); + exit(1); + } + found++; + } + return found; +} + +void setup_futex_sems(struct sem_wakeup_info **wi, int num_semids) +{ + return; +} + +void cleanup_futex_sems(int num) +{ + return; +} + +struct sem_operations futex_sem_ops = { + .wait = wait_futex_sem, + .wake = futex_wake_some, + .setup = setup_futex_sems, + .cleanup = cleanup_futex_sems, + .name = "futex sem operations", +}; + +/* nanosleep sems here */ +void wait_nanosleep_sem(struct lockinfo *l) +{ + int ret; + struct timespec tv = { 0, 1000000 }; + int count = 0; + + l->data = 1; + worklist_add(l); + while(l->data) { + ret = nanosleep(&tv, NULL); + if (ret) { + perror("nanosleep"); + exit(1); + } + count++; + } +} + +int nanosleep_wake_some(struct sem_wakeup_info *wi, int num_semids, int num) +{ + int i; + struct lockinfo *l; + + for (i = 0; i < num; i++) { + l = worklist_rm(); + if (!l) + break; + if (l->data != 1) + fprintf(stderr, "warning, lockinfo data was %d\n", + l->data); + l->data = 0; + } + return i; +} + +void setup_nanosleep_sems(struct sem_wakeup_info **wi, int num_semids) +{ + return; +} + +void cleanup_nanosleep_sems(int num) +{ + return; +} + +struct sem_operations nanosleep_sem_ops = { + .wait = wait_nanosleep_sem, + .wake = nanosleep_wake_some, + .setup = setup_nanosleep_sems, + .cleanup = cleanup_nanosleep_sems, + .name = "nano sleep sem operations", +}; + +void *worker(void *arg) +{ + struct lockinfo *l = (struct lockinfo *)arg; + int burn_count = 0; + pthread_t tid = pthread_self(); + size_t pagesize = getpagesize(); + char *buf = malloc(pagesize); + + if (!buf) { + perror("malloc"); + exit(1); + } + + l->tid = tid; + workers_started = 1; + smp_mb(); + + while(!all_done) { + l->ops->wait(l); + if (all_done) + break; + burn_count++; + } + pthread_mutex_lock(&worklist_mutex); + total_burns += burn_count; + if (burn_count < min_burns) + min_burns = burn_count; + if (burn_count > max_burns) + max_burns = burn_count; + thread_count--; + pthread_mutex_unlock(&worklist_mutex); + return (void *)0; +} + +void print_usage(void) +{ + printf("usage: sembench [-t threads] [-w wake incr] [-r runtime]"); + printf(" [-o num] (0=ipc, 1=nanosleep, 2=futex)\n"); + exit(1); +} + +#define NUM_OPERATIONS 3 +struct sem_operations *allops[NUM_OPERATIONS] = { &ipc_sem_ops, + &nanosleep_sem_ops, + &futex_sem_ops}; + +int main(int ac, char **av) { + int ret; + int i; + int semid = 0; + int sem_num = 0; + int burn_count = 0; + struct sem_wakeup_info *wi = NULL; + struct timeval start; + struct timeval now; + int num_semids = 0; + int wake_num = 256; + int run_secs = 30; + int pagesize = getpagesize(); + char *buf = malloc(pagesize); + struct sem_operations *ops = allops[0]; + cpu_set_t cpu_mask; + cpu_set_t target_mask; + int target_cpu = 0; + int max_cpu = -1; + + if (!buf) { + perror("malloc"); + exit(1); + } + for (i = 1; i < ac; i++) { + if (strcmp(av[i], "-t") == 0) { + if (i == ac -1) + print_usage(); + num_threads = atoi(av[i+1]); + i++; + } else if (strcmp(av[i], "-w") == 0) { + if (i == ac -1) + print_usage(); + wake_num = atoi(av[i+1]); + i++; + } else if (strcmp(av[i], "-r") == 0) { + if (i == ac -1) + print_usage(); + run_secs = atoi(av[i+1]); + i++; + } else if (strcmp(av[i], "-o") == 0) { + int index; + if (i == ac -1) + print_usage(); + index = atoi(av[i+1]); + if (index >= NUM_OPERATIONS) { + fprintf(stderr, "invalid operations %d\n", + index); + exit(1); + } + ops = allops[index]; + i++; + } else if (strcmp(av[i], "-T") == 0) { + timeout_test = 1; + } else if (strcmp(av[i], "-h") == 0) { + print_usage(); + } + } + num_semids = (num_threads + SEMS_PERID - 1) / SEMS_PERID; + ops->setup(&wi, num_semids); + + ret = sched_getaffinity(0, sizeof(cpu_set_t), &cpu_mask); + if (ret) { + perror("sched_getaffinity"); + exit(1); + } + for (i = 0; i < CPU_SETSIZE; i++) + if (CPU_ISSET(i, &cpu_mask)) + max_cpu = i; + if (max_cpu == -1) { + fprintf(stderr, "sched_getaffinity returned empty mask\n"); + exit(1); + } + + CPU_ZERO(&target_mask); + + worklist = malloc(sizeof(*worklist) * num_threads); + memset(worklist, 0, sizeof(*worklist) * num_threads); + + for (i = 0; i < num_threads; i++) { + struct lockinfo *l; + pthread_t tid; + thread_count++; + l = worklist + i; + if (!l) { + perror("malloc"); + exit(1); + } + l->id = semid; + l->index = sem_num++; + l->ops = ops; + if (sem_num >= SEMS_PERID) { + semid++; + sem_num = 0; + } + ret = pthread_create(&tid, NULL, worker, (void *)l); + if (ret) { + perror("pthread_create"); + exit(1); + } + + while (!CPU_ISSET(target_cpu, &cpu_mask)) { + target_cpu++; + if (target_cpu > max_cpu) + target_cpu = 0; + } + CPU_SET(target_cpu, &target_mask); + ret = pthread_setaffinity_np(tid, sizeof(cpu_set_t), + &target_mask); + CPU_CLR(target_cpu, &target_mask); + target_cpu++; + + ret = pthread_detach(tid); + if (ret) { + perror("pthread_detach"); + exit(1); + } + } + while(!workers_started) { + smp_mb(); + usleep(200); + } + gettimeofday(&start, NULL); + fprintf(stderr, "main loop going\n"); + while(1) { + ops->wake(wi, num_semids, wake_num); + burn_count++; + gettimeofday(&now, NULL); + if (now.tv_sec - start.tv_sec >= run_secs) + break; + } + fprintf(stderr, "all done\n"); + all_done = 1; + while(thread_count > 0) { + ops->wake(wi, num_semids, wake_num); + usleep(200); + } + printf("%d threads, waking %d at a time\n", num_threads, wake_num); + printf("using %s\n", ops->name); + printf("main thread burns: %d\n", burn_count); + printf("worker burn count total %lu min %lu max %lu avg %lu\n", + total_burns, min_burns, max_burns, total_burns / num_threads); + printf("run time %d seconds %lu worker burns per second\n", + (int)(now.tv_sec - start.tv_sec), + total_burns / (now.tv_sec - start.tv_sec)); + ops->cleanup(num_semids); + return 0; +} + diff --git a/drivers/staging/ktap/test/bench/test.sh b/drivers/staging/ktap/test/bench/test.sh new file mode 100644 index 000000000000..9f77969b6f2b --- /dev/null +++ b/drivers/staging/ktap/test/bench/test.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +gcc -o sembench sembench.c -O2 -lpthread + +COMMAND="./sembench -t 200 -w 20 -r 30 -o 2" + +echo -e "\n\t\tPass 1 without tracing" +$COMMAND +echo -e "\n\t\tPass 2 without tracing" +$COMMAND +echo -e "\n\t\tPass 3 without tracing" +$COMMAND + +echo "" + +KTAP_ONE_LINER="trace syscalls:sys_*_futex {}" + +echo -e "\n\t\tPass 1 with tracing" +../../ktap -e "$KTAP_ONE_LINER" -- $COMMAND +echo -e "\n\t\tPass 2 with tracing" +../../ktap -e "$KTAP_ONE_LINER" -- $COMMAND +echo -e "\n\t\tPass 3 with tracing" +../../ktap -e "$KTAP_ONE_LINER" -- $COMMAND + +rm -rf ./sembench diff --git a/drivers/staging/ktap/test/concat.kp b/drivers/staging/ktap/test/concat.kp new file mode 100644 index 000000000000..be77bb70ea8e --- /dev/null +++ b/drivers/staging/ktap/test/concat.kp @@ -0,0 +1,15 @@ +#!/usr/bin/env ktap + +function failed() { + printf("failed\n"); + exit(-1); +} + +#----------------------------------------# + +a = "123" +b = "456" + +if (a..b != "123456") { + failed() +} diff --git a/drivers/staging/ktap/test/count.kp b/drivers/staging/ktap/test/count.kp new file mode 100644 index 000000000000..26f962c227a4 --- /dev/null +++ b/drivers/staging/ktap/test/count.kp @@ -0,0 +1,20 @@ +#!/usr/bin/env ktap + +function failed() { + printf("failed\n"); + exit(-1); +} + +#---------------------------------------# + +t = {} + +t["key"] += 1 +if (t["key"] != 1) { + failed() +} + +t["key"] += 1 +if (t["key"] != 2) { + failed() +} diff --git a/drivers/staging/ktap/test/fibonacci.kp b/drivers/staging/ktap/test/fibonacci.kp new file mode 100644 index 000000000000..7e141da0de42 --- /dev/null +++ b/drivers/staging/ktap/test/fibonacci.kp @@ -0,0 +1,36 @@ +#!/usr/bin/env ktap + +function failed() { + printf("failed\n"); + exit(-1); +} + +#---------------fibonacci---------------- + + +#regular recursive fibonacci +function fib(n) { + if (n < 2) { + return n + } + return fib(n-1) + fib(n-2) +} + +if (fib(20) != 6765) { + failed() +} + +#tail recursive fibonacci +function fib(n) { + f = function (iter, res, next) { + if (iter == 0) { + return res; + } + return f(iter-1, next, res+next) + } + return f(n, 0, 1) +} + +if (fib(20) != 6765) { + failed() +} diff --git a/drivers/staging/ktap/test/function.kp b/drivers/staging/ktap/test/function.kp new file mode 100644 index 000000000000..bfbff2614104 --- /dev/null +++ b/drivers/staging/ktap/test/function.kp @@ -0,0 +1,88 @@ +#!/usr/bin/env ktap + +function failed() { + printf("failed\n"); + exit(-1); +} + +### basic function call ### +function f1(a, b) { + return a + b +} + +if (f1(2, 3) != 5) { + failed(); +} + +### return string ### +function f2() { + return "function return" +} + +if (f2() != "function return") { + failed(); +} + +### mutli-value return ### +function f3(a, b) { + return a+b, a-b; +} + +c, d = f3(2, 3); +if(c != 5 || d != -1) { + failed(); +} + + +### closure testing ### +function f4() { + f5 = function(a, b) { + return a * b + } + return f5 +} + +local f = f4() +if (f(9, 9) != 81) { + failed(); +} + +### closure with lexcial variable ### +# issue: variable cannot be local +i = 1 +function f6() { + i = 5 + f7 = function(a, b) { + return a * b + i + } + return f7 +} + +f = f6() +if (f(9, 9) != 81 + i) { + failed(); +} + +i = 6 +if (f(9, 9) != 81 + i) { + failed(); +} + +### tail call +### stack should not overflow in tail call mechanism +a = 0 +function f8(i) { + if (i == 1000000) { + a = 1000000 + return + } + # must add return here, otherwise stack overflow + return f8(i+1) +} + +f8(0) +if (a != 1000000) { + failed(); +} + + diff --git a/drivers/staging/ktap/test/if.kp b/drivers/staging/ktap/test/if.kp new file mode 100644 index 000000000000..3122084af5ee --- /dev/null +++ b/drivers/staging/ktap/test/if.kp @@ -0,0 +1,24 @@ +#!/usr/bin/env ktap + +function failed() { + printf("failed\n"); + exit(-1); +} + +#-----------------------------------------# + +if (false) { + failed() +} + +if (nil) { + failed() +} + +# ktap only think false and nil is "real false", number 0 is true +# it's same as lua +# Might change it in future, to make similar with C +if (0) { + #failed() +} + diff --git a/drivers/staging/ktap/test/kprobe.kp b/drivers/staging/ktap/test/kprobe.kp new file mode 100644 index 000000000000..022d4a70b31f --- /dev/null +++ b/drivers/staging/ktap/test/kprobe.kp @@ -0,0 +1,14 @@ +#!/usr/bin/env ktap + +n = 0 +trace probe:schedule { + n = n + 1 +} + +tick-1s { + if (n == 0) { + printf("failed\n"); + } + exit() +} + diff --git a/drivers/staging/ktap/test/kretprobe.kp b/drivers/staging/ktap/test/kretprobe.kp new file mode 100644 index 000000000000..e311e84c292f --- /dev/null +++ b/drivers/staging/ktap/test/kretprobe.kp @@ -0,0 +1,14 @@ +#!/usr/bin/env ktap + +n = 0 +trace probe:__schedule%return { + n = n + 1 +} + +tick-1s { + if (n == 0) { + printf("failed\n"); + } + exit() +} + diff --git a/drivers/staging/ktap/test/len.kp b/drivers/staging/ktap/test/len.kp new file mode 100644 index 000000000000..697d91553ba9 --- /dev/null +++ b/drivers/staging/ktap/test/len.kp @@ -0,0 +1,25 @@ +#!/usr/bin/env ktap + +function failed() { + printf("failed\n"); + exit(-1); +} + +#-----------------------------------------# + +a = "123456789" + +if (len(a) != 9) { + failed() +} + +b = {} +b[0] = 0 +b[1] = 1 +b["keys"] = "values" + +if (len(b) != 3) { + failed() +} + + diff --git a/drivers/staging/ktap/test/looping.kp b/drivers/staging/ktap/test/looping.kp new file mode 100644 index 000000000000..fe48051a29c2 --- /dev/null +++ b/drivers/staging/ktap/test/looping.kp @@ -0,0 +1,40 @@ +#!/usr/bin/env ktap + +function failed() { + printf("failed\n"); + exit(-1); +} + +### basic while-loop testing +a = 1 +while (a < 1000) { + a = a + 1 +} + +if (a != 1000) { + failed() +} + +### break testing +### Note that ktap don't have continue keyword +a = 1 +while (a < 1000) { + if (a == 10) { + break + } + a = a + 1 +} + +if (a != 10) { + failed() +} + +### for-loop testing +b=0 +for (c = 0, 1000, 1) { + b = b + 1 +} + +if (b != 1001) { + failed() +} diff --git a/drivers/staging/ktap/test/pairs.kp b/drivers/staging/ktap/test/pairs.kp new file mode 100644 index 000000000000..cdf3825951dd --- /dev/null +++ b/drivers/staging/ktap/test/pairs.kp @@ -0,0 +1,45 @@ +#!/usr/bin/env ktap + +function failed() { + printf("failed\n"); + exit(-1); +} + +#-----------------------------------------# + +t = {} +t[1] = 101 +t[2] = 102 +t[3] = 103 +t["key_1"] = "value_1" +t["key_2"] = "value_2" +t["key_3"] = "value_3" + +local n = 0 + +for (k, v in pairs(t)) { + n = n + 1 + + if (k == 1 && v != 101) { + failed() + } + if (k == 2 && v != 102) { + failed() + } + if (k == 3 && v != 103) { + failed() + } + if (k == "key_1" && v != "value_1") { + failed() + } + if (k == "key_2" && v != "value_2") { + failed() + } + if (k == "key_3" && v != "value_3") { + failed() + } +} + +if (n != len(t)) { + failed() +} diff --git a/drivers/staging/ktap/test/run_test.sh b/drivers/staging/ktap/test/run_test.sh new file mode 100644 index 000000000000..b98af2659123 --- /dev/null +++ b/drivers/staging/ktap/test/run_test.sh @@ -0,0 +1,58 @@ +#!/bin/sh + +rmmod ktapvm > /dev/null 2>&1 +insmod ../ktapvm.ko +if test $? -ne 0; then + echo "Cannot insmod ../ktapvm.ko" + exit -1 +fi + +KTAP=../ktap +function ktaprun { + echo "$KTAP $@" + $KTAP $@ +} + + + +####################################################### +# Use $ktap directly if the arguments contains strings +$KTAP arg.kp 1 testing "2 3 4" +$KTAP -e 'print("one-liner testing")' +$KTAP -e 'exit()' +$KTAP -o /dev/null -e 'trace syscalls:* { print(argevent) }' \ + -- ls > /devnull + +$KTAP -o /dev/null -e 'trace syscalls:* { print(argevent) }' \ + -- $KTAP -e 'while (1) {}' + +ktaprun arith.kp +ktaprun concat.kp +ktaprun count.kp +ktaprun fibonacci.kp +ktaprun function.kp +ktaprun if.kp +ktaprun kprobe.kp +ktaprun kretprobe.kp +ktaprun len.kp +ktaprun looping.kp +ktaprun pairs.kp +ktaprun table.kp +ktaprun aggr_table.kp +ktaprun timer.kp +ktaprun tracepoint.kp +ktaprun -o /dev/null zerodivide.kp +ktaprun ansi.kp + +echo "testing kill deadloop ktap script" +$KTAP -e 'while (1) {}' & +pkill ktap +sleep 1 + +##################################################### +rmmod ktapvm +if test $? -ne 0; then + echo "Error in rmmod ../ktapvm.ko, leak module refcount?" + exit -1 +fi + diff --git a/drivers/staging/ktap/test/table.kp b/drivers/staging/ktap/test/table.kp new file mode 100644 index 000000000000..1f6d6e43302f --- /dev/null +++ b/drivers/staging/ktap/test/table.kp @@ -0,0 +1,71 @@ +#!/usr/bin/env ktap + +function failed() { + printf("failed\n"); + exit(-1); +} + +### table testing ### +x = {} +x[1] = "1" +if (x[1] != "1") { + failed() +} + +x[1] = 22222222222222222222222222222222222222222 +if (x[1] != 22222222222222222222222222222222222222222) { + failed() +} + +x[1] = "jovi" +if (x[1] != "jovi") { + failed() +} + +x[11111111111111111111111111111111] = "jovi" +if (x[11111111111111111111111111111111] != "jovi") { + failed() +} + +x["jovi"] = 1 +if (x["jovi"] != 1) { + failed() +} + +x["long string....................................."] = 1 +if (x["long string....................................."] != 1) { + failed() +} + +# issue: subx must declare firstly, otherwise kernel will oops +subx = {} +subx["test"] = "this is test" +x["test"] = subx +if (x["test"]["test"] != "this is test") { + failed() +} + +tbl = {} +i = 1 +while (i < 100000) { + tbl[i] = i + i = i + 1 +} + +i = 1 +while (i < 100000) { + if (tbl[i] != i) { + failed() + } + i = i + 1 +} + +#### table initization +days = {"Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday"} + +if (days[2] != "Monday") { + failed() +} + + diff --git a/drivers/staging/ktap/test/timer.kp b/drivers/staging/ktap/test/timer.kp new file mode 100644 index 000000000000..02e8a61da664 --- /dev/null +++ b/drivers/staging/ktap/test/timer.kp @@ -0,0 +1,28 @@ +#!/usr/bin/env ktap + +function failed() { + printf("failed\n"); + exit(-1); +} + +#---------------------------------------# + +n1 = 0 +n2 = 0 + +tick-1s { + n1 = n1 + 1 +} + +tick-1s { + n2 = n2 + 1 +} + +tick-4s { + if (n1 == 0 || n2 == 0) { + failed() + } + exit() +} + + diff --git a/drivers/staging/ktap/test/tracepoint.kp b/drivers/staging/ktap/test/tracepoint.kp new file mode 100644 index 000000000000..fb036e675a75 --- /dev/null +++ b/drivers/staging/ktap/test/tracepoint.kp @@ -0,0 +1,22 @@ +#!/usr/bin/env ktap + +function failed() { + printf("failed\n"); + exit(-1); +} + +#----------------------------------------# + +n = 0 + +trace sched:* { + n = n + 1 +} + +tick-1s { + if (n == 0) { + failed() + } + exit() +} + diff --git a/drivers/staging/ktap/test/zerodivide.kp b/drivers/staging/ktap/test/zerodivide.kp new file mode 100644 index 000000000000..abb1eae4fdab --- /dev/null +++ b/drivers/staging/ktap/test/zerodivide.kp @@ -0,0 +1,5 @@ +#!/usr/bin/env ktap + +a = 1/0 +#should not go here +printf("Failed\n") diff --git a/drivers/staging/ktap/userspace/code.c b/drivers/staging/ktap/userspace/code.c new file mode 100644 index 000000000000..1427fd518dec --- /dev/null +++ b/drivers/staging/ktap/userspace/code.c @@ -0,0 +1,968 @@ +/* + * code.c - Code generator for ktap + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * Copyright (C) 1994-2013 Lua.org, PUC-Rio. + * - The part of code in this file is copied from lua initially. + * - lua's MIT license is compatible with GPL. + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +#include "../include/ktap_types.h" +#include "../include/ktap_opcodes.h" +#include "ktapc.h" + + +#define hasjumps(e) ((e)->t != (e)->f) + +void codegen_patchtohere (ktap_funcstate *fs, int list); + +static int isnumeral(ktap_expdesc *e) +{ + return (e->k == VKNUM && e->t == NO_JUMP && e->f == NO_JUMP); +} + +void codegen_nil(ktap_funcstate *fs, int from, int n) +{ + ktap_instruction *previous; + int l = from + n - 1; /* last register to set nil */ + + if (fs->pc > fs->lasttarget) { /* no jumps to current position? */ + previous = &fs->f->code[fs->pc-1]; + if (GET_OPCODE(*previous) == OP_LOADNIL) { + int pfrom = GETARG_A(*previous); + int pl = pfrom + GETARG_B(*previous); + + if ((pfrom <= from && from <= pl + 1) || + (from <= pfrom && pfrom <= l + 1)) { /* can connect both? */ + if (pfrom < from) + from = pfrom; /* from = min(from, pfrom) */ + if (pl > l) + l = pl; /* l = max(l, pl) */ + SETARG_A(*previous, from); + SETARG_B(*previous, l - from); + return; + } + } /* else go through */ + } + codegen_codeABC(fs, OP_LOADNIL, from, n - 1, 0); /* else no optimization */ +} + +int codegen_jump(ktap_funcstate *fs) +{ + int jpc = fs->jpc; /* save list of jumps to here */ + int j; + + fs->jpc = NO_JUMP; + j = codegen_codeAsBx(fs, OP_JMP, 0, NO_JUMP); + codegen_concat(fs, &j, jpc); /* keep them on hold */ + return j; +} + +void codegen_ret(ktap_funcstate *fs, int first, int nret) +{ + codegen_codeABC(fs, OP_RETURN, first, nret+1, 0); +} + +static int condjump(ktap_funcstate *fs, OpCode op, int A, int B, int C) +{ + codegen_codeABC(fs, op, A, B, C); + return codegen_jump(fs); +} + +static void fixjump(ktap_funcstate *fs, int pc, int dest) +{ + ktap_instruction *jmp = &fs->f->code[pc]; + int offset = dest-(pc+1); + + ktap_assert(dest != NO_JUMP); + if (abs(offset) > MAXARG_sBx) + lex_syntaxerror(fs->ls, "control structure too long"); + SETARG_sBx(*jmp, offset); +} + +/* + * returns current `pc' and marks it as a jump target (to avoid wrong + * optimizations with consecutive instructions not in the same basic block). + */ +int codegen_getlabel(ktap_funcstate *fs) +{ + fs->lasttarget = fs->pc; + return fs->pc; +} + +static int getjump(ktap_funcstate *fs, int pc) +{ + int offset = GETARG_sBx(fs->f->code[pc]); + + if (offset == NO_JUMP) /* point to itself represents end of list */ + return NO_JUMP; /* end of list */ + else + return (pc+1)+offset; /* turn offset into absolute position */ +} + +static ktap_instruction *getjumpcontrol(ktap_funcstate *fs, int pc) +{ + ktap_instruction *pi = &fs->f->code[pc]; + if (pc >= 1 && testTMode(GET_OPCODE(*(pi-1)))) + return pi-1; + else + return pi; +} + +/* + * check whether list has any jump that do not produce a value + * (or produce an inverted value) + */ +static int need_value(ktap_funcstate *fs, int list) +{ + for (; list != NO_JUMP; list = getjump(fs, list)) { + ktap_instruction i = *getjumpcontrol(fs, list); + if (GET_OPCODE(i) != OP_TESTSET) + return 1; + } + return 0; /* not found */ +} + +static int patchtestreg(ktap_funcstate *fs, int node, int reg) +{ + ktap_instruction *i = getjumpcontrol(fs, node); + if (GET_OPCODE(*i) != OP_TESTSET) + return 0; /* cannot patch other instructions */ + if (reg != NO_REG && reg != GETARG_B(*i)) + SETARG_A(*i, reg); + else /* no register to put value or register already has the value */ + *i = CREATE_ABC(OP_TEST, GETARG_B(*i), 0, GETARG_C(*i)); + + return 1; +} + +static void removevalues(ktap_funcstate *fs, int list) +{ + for (; list != NO_JUMP; list = getjump(fs, list)) + patchtestreg(fs, list, NO_REG); +} + +static void patchlistaux(ktap_funcstate *fs, int list, int vtarget, int reg, + int dtarget) +{ + while (list != NO_JUMP) { + int next = getjump(fs, list); + if (patchtestreg(fs, list, reg)) + fixjump(fs, list, vtarget); + else + fixjump(fs, list, dtarget); /* jump to default target */ + list = next; + } +} + +static void dischargejpc(ktap_funcstate *fs) +{ + patchlistaux(fs, fs->jpc, fs->pc, NO_REG, fs->pc); + fs->jpc = NO_JUMP; +} + +void codegen_patchlist(ktap_funcstate *fs, int list, int target) +{ + if (target == fs->pc) + codegen_patchtohere(fs, list); + else { + ktap_assert(target < fs->pc); + patchlistaux(fs, list, target, NO_REG, target); + } +} + +void codegen_patchclose(ktap_funcstate *fs, int list, int level) +{ + level++; /* argument is +1 to reserve 0 as non-op */ + while (list != NO_JUMP) { + int next = getjump(fs, list); + ktap_assert(GET_OPCODE(fs->f->code[list]) == OP_JMP && + (GETARG_A(fs->f->code[list]) == 0 || + GETARG_A(fs->f->code[list]) >= level)); + SETARG_A(fs->f->code[list], level); + list = next; + } +} + +void codegen_patchtohere(ktap_funcstate *fs, int list) +{ + codegen_getlabel(fs); + codegen_concat(fs, &fs->jpc, list); +} + +void codegen_concat(ktap_funcstate *fs, int *l1, int l2) +{ + if (l2 == NO_JUMP) + return; + else if (*l1 == NO_JUMP) + *l1 = l2; + else { + int list = *l1; + int next; + while ((next = getjump(fs, list)) != NO_JUMP) /* find last element */ + list = next; + fixjump(fs, list, l2); + } +} + +static int codegen_code(ktap_funcstate *fs, ktap_instruction i) +{ + ktap_proto *f = fs->f; + + dischargejpc(fs); /* `pc' will change */ + + /* put new instruction in code array */ + ktapc_growvector(f->code, fs->pc, f->sizecode, ktap_instruction, + MAX_INT, "opcodes"); + f->code[fs->pc] = i; + + /* save corresponding line information */ + ktapc_growvector(f->lineinfo, fs->pc, f->sizelineinfo, int, + MAX_INT, "opcodes"); + f->lineinfo[fs->pc] = fs->ls->lastline; + return fs->pc++; +} + +int codegen_codeABC(ktap_funcstate *fs, OpCode o, int a, int b, int c) +{ + ktap_assert(getOpMode(o) == iABC); + //ktap_assert(getBMode(o) != OpArgN || b == 0); + //ktap_assert(getCMode(o) != OpArgN || c == 0); + //ktap_assert(a <= MAXARG_A && b <= MAXARG_B && c <= MAXARG_C); + return codegen_code(fs, CREATE_ABC(o, a, b, c)); +} + +int codegen_codeABx(ktap_funcstate *fs, OpCode o, int a, unsigned int bc) +{ + ktap_assert(getOpMode(o) == iABx || getOpMode(o) == iAsBx); + ktap_assert(getCMode(o) == OpArgN); + ktap_assert(a <= MAXARG_A && bc <= MAXARG_Bx); + return codegen_code(fs, CREATE_ABx(o, a, bc)); +} + +static int codeextraarg(ktap_funcstate *fs, int a) +{ + ktap_assert(a <= MAXARG_Ax); + return codegen_code(fs, CREATE_Ax(OP_EXTRAARG, a)); +} + +int codegen_codek(ktap_funcstate *fs, int reg, int k) +{ + if (k <= MAXARG_Bx) + return codegen_codeABx(fs, OP_LOADK, reg, k); + else { + int p = codegen_codeABx(fs, OP_LOADKX, reg, 0); + codeextraarg(fs, k); + return p; + } +} + +void codegen_checkstack(ktap_funcstate *fs, int n) +{ + int newstack = fs->freereg + n; + + if (newstack > fs->f->maxstacksize) { + if (newstack >= MAXSTACK) + lex_syntaxerror(fs->ls, "function or expression too complex"); + fs->f->maxstacksize = (u8)(newstack); + } +} + +void codegen_reserveregs(ktap_funcstate *fs, int n) +{ + codegen_checkstack(fs, n); + fs->freereg += n; +} + +static void freereg(ktap_funcstate *fs, int reg) +{ + if (!ISK(reg) && reg >= fs->nactvar) { + fs->freereg--; + ktap_assert(reg == fs->freereg); + } +} + +static void freeexp(ktap_funcstate *fs, ktap_expdesc *e) +{ + if (e->k == VNONRELOC) + freereg(fs, e->u.info); +} + +static int addk(ktap_funcstate *fs, ktap_value *key, ktap_value *v) +{ + const ktap_value *idx = ktapc_table_get(fs->h, key); + ktap_proto *f = fs->f; + ktap_value kn; + int k, oldsize; + + if (ttisnumber(idx)) { + ktap_number n = nvalue(idx); + ktap_number2int(k, n); + if (ktapc_equalobj(&f->k[k], v)) + return k; + /* else may be a collision (e.g., between 0.0 and "\0\0\0\0\0\0\0\0"); + go through and create a new entry for this value */ + } + /* constant not found; create a new entry */ + oldsize = f->sizek; + k = fs->nk; + + /* numerical value does not need GC barrier; + table has no metatable, so it does not need to invalidate cache */ + setnvalue(&kn, (ktap_number)k); + ktapc_table_setvalue(fs->h, key, &kn); + ktapc_growvector(f->k, k, f->sizek, ktap_value, MAXARG_Ax, "constants"); + while (oldsize < f->sizek) + setnilvalue(&f->k[oldsize++]); + setobj(&f->k[k], v); + fs->nk++; + return k; +} + +int codegen_stringK(ktap_funcstate *fs, ktap_string *s) +{ + ktap_value o; + + setsvalue(&o, s); + return addk(fs, &o, &o); +} + +int codegen_numberK(ktap_funcstate *fs, ktap_number r) +{ + int n; + ktap_value o, s; + + setnvalue(&o, r); + if (r == 0 || ktap_numisnan(NULL, r)) { /* handle -0 and NaN */ + /* use raw representation as key to avoid numeric problems */ + setsvalue(&s, ktapc_ts_newlstr((char *)&r, sizeof(r))); + // incr_top(L); + n = addk(fs, &s, &o); + // L->top--; + } else + n = addk(fs, &o, &o); /* regular case */ + return n; +} + +static int boolK(ktap_funcstate *fs, int b) +{ + ktap_value o; + setbvalue(&o, b); + return addk(fs, &o, &o); +} + +static int nilK(ktap_funcstate *fs) +{ + ktap_value k, v; + setnilvalue(&v); + /* cannot use nil as key; instead use table itself to represent nil */ + sethvalue(&k, fs->h); + return addk(fs, &k, &v); +} + +void codegen_setreturns(ktap_funcstate *fs, ktap_expdesc *e, int nresults) +{ + if (e->k == VCALL) { /* expression is an open function call? */ + SETARG_C(getcode(fs, e), nresults+1); + } + else if (e->k == VVARARG) { + SETARG_B(getcode(fs, e), nresults+1); + SETARG_A(getcode(fs, e), fs->freereg); + codegen_reserveregs(fs, 1); + } +} + +void codegen_setoneret(ktap_funcstate *fs, ktap_expdesc *e) +{ + if (e->k == VCALL) { /* expression is an open function call? */ + e->k = VNONRELOC; + e->u.info = GETARG_A(getcode(fs, e)); + } else if (e->k == VVARARG) { + SETARG_B(getcode(fs, e), 2); + e->k = VRELOCABLE; /* can relocate its simple result */ + } +} + +void codegen_dischargevars(ktap_funcstate *fs, ktap_expdesc *e) +{ + switch (e->k) { + case VLOCAL: { + e->k = VNONRELOC; + break; + } + case VUPVAL: { + e->u.info = codegen_codeABC(fs, OP_GETUPVAL, 0, e->u.info, 0); + e->k = VRELOCABLE; + break; + } + case VINDEXED: { + OpCode op = OP_GETTABUP; /* assume 't' is in an upvalue */ + freereg(fs, e->u.ind.idx); + if (e->u.ind.vt == VLOCAL) { /* 't' is in a register? */ + freereg(fs, e->u.ind.t); + op = OP_GETTABLE; + } + e->u.info = codegen_codeABC(fs, op, 0, e->u.ind.t, e->u.ind.idx); + e->k = VRELOCABLE; + break; + } + case VVARARG: + case VCALL: { + codegen_setoneret(fs, e); + break; + } + default: + break; /* there is one value available (somewhere) */ + } +} + +static int code_label(ktap_funcstate *fs, int A, int b, int jump) +{ + codegen_getlabel(fs); /* those instructions may be jump targets */ + return codegen_codeABC(fs, OP_LOADBOOL, A, b, jump); +} + +static void discharge2reg(ktap_funcstate *fs, ktap_expdesc *e, int reg) +{ + codegen_dischargevars(fs, e); + switch (e->k) { + case VNIL: { + codegen_nil(fs, reg, 1); + break; + } + case VFALSE: case VTRUE: { + codegen_codeABC(fs, OP_LOADBOOL, reg, e->k == VTRUE, 0); + break; + } + case VEVENT: + codegen_codeABC(fs, OP_EVENT, reg, 0, 0); + break; + case VEVENTNAME: + codegen_codeABC(fs, OP_EVENTNAME, reg, 0, 0); + break; + case VEVENTARG: + codegen_codeABC(fs, OP_EVENTARG, reg, e->u.info, 0); + break; + case VK: { + codegen_codek(fs, reg, e->u.info); + break; + } + case VKNUM: { + codegen_codek(fs, reg, codegen_numberK(fs, e->u.nval)); + break; + } + case VRELOCABLE: { + ktap_instruction *pc = &getcode(fs, e); + SETARG_A(*pc, reg); + break; + } + case VNONRELOC: { + if (reg != e->u.info) + codegen_codeABC(fs, OP_MOVE, reg, e->u.info, 0); + break; + } + default: + ktap_assert(e->k == VVOID || e->k == VJMP); + return; /* nothing to do... */ + } + + e->u.info = reg; + e->k = VNONRELOC; +} + +static void discharge2anyreg(ktap_funcstate *fs, ktap_expdesc *e) +{ + if (e->k != VNONRELOC) { + codegen_reserveregs(fs, 1); + discharge2reg(fs, e, fs->freereg-1); + } +} + +static void exp2reg(ktap_funcstate *fs, ktap_expdesc *e, int reg) +{ + discharge2reg(fs, e, reg); + if (e->k == VJMP) + codegen_concat(fs, &e->t, e->u.info); /* put this jump in `t' list */ + if (hasjumps(e)) { + int final; /* position after whole expression */ + int p_f = NO_JUMP; /* position of an eventual LOAD false */ + int p_t = NO_JUMP; /* position of an eventual LOAD true */ + + if (need_value(fs, e->t) || need_value(fs, e->f)) { + int fj = (e->k == VJMP) ? NO_JUMP : codegen_jump(fs); + + p_f = code_label(fs, reg, 0, 1); + p_t = code_label(fs, reg, 1, 0); + codegen_patchtohere(fs, fj); + } + final = codegen_getlabel(fs); + patchlistaux(fs, e->f, final, reg, p_f); + patchlistaux(fs, e->t, final, reg, p_t); + } + e->f = e->t = NO_JUMP; + e->u.info = reg; + e->k = VNONRELOC; +} + +void codegen_exp2nextreg(ktap_funcstate *fs, ktap_expdesc *e) +{ + codegen_dischargevars(fs, e); + freeexp(fs, e); + codegen_reserveregs(fs, 1); + exp2reg(fs, e, fs->freereg - 1); +} + +int codegen_exp2anyreg(ktap_funcstate *fs, ktap_expdesc *e) +{ + codegen_dischargevars(fs, e); + if (e->k == VNONRELOC) { + if (!hasjumps(e)) + return e->u.info; /* exp is already in a register */ + if (e->u.info >= fs->nactvar) { /* reg. is not a local? */ + exp2reg(fs, e, e->u.info); /* put value on it */ + return e->u.info; + } + } + codegen_exp2nextreg(fs, e); /* default */ + return e->u.info; +} + +void codegen_exp2anyregup(ktap_funcstate *fs, ktap_expdesc *e) +{ + if (e->k != VUPVAL || hasjumps(e)) + codegen_exp2anyreg(fs, e); +} + +void codegen_exp2val(ktap_funcstate *fs, ktap_expdesc *e) +{ + if (hasjumps(e)) + codegen_exp2anyreg(fs, e); + else + codegen_dischargevars(fs, e); +} + +int codegen_exp2RK(ktap_funcstate *fs, ktap_expdesc *e) +{ + codegen_exp2val(fs, e); + switch (e->k) { + case VTRUE: + case VFALSE: + case VNIL: { + if (fs->nk <= MAXINDEXRK) { /* constant fits in RK operand? */ + e->u.info = (e->k == VNIL) ? nilK(fs) : + boolK(fs, (e->k == VTRUE)); + e->k = VK; + return RKASK(e->u.info); + } + else + break; + } + case VKNUM: { + e->u.info = codegen_numberK(fs, e->u.nval); + e->k = VK; + /* go through */ + } + case VK: { + if (e->u.info <= MAXINDEXRK) /* constant fits in argC? */ + return RKASK(e->u.info); + else + break; + } + default: + break; + } + /* not a constant in the right range: put it in a register */ + return codegen_exp2anyreg(fs, e); +} + +void codegen_storevar(ktap_funcstate *fs, ktap_expdesc *var, ktap_expdesc *ex) +{ + switch (var->k) { + case VLOCAL: { + freeexp(fs, ex); + exp2reg(fs, ex, var->u.info); + return; + } + case VUPVAL: { + int e = codegen_exp2anyreg(fs, ex); + codegen_codeABC(fs, OP_SETUPVAL, e, var->u.info, 0); + break; + } + case VINDEXED: { + OpCode op = (var->u.ind.vt == VLOCAL) ? OP_SETTABLE : OP_SETTABUP; + int e = codegen_exp2RK(fs, ex); + codegen_codeABC(fs, op, var->u.ind.t, var->u.ind.idx, e); + break; + } + default: + ktap_assert(0); /* invalid var kind to store */ + break; + } + + freeexp(fs, ex); +} + +void codegen_storeincr(ktap_funcstate *fs, ktap_expdesc *var, ktap_expdesc *ex) +{ + switch (var->k) { +#if 0 /*current not supported */ + case VLOCAL: { + freeexp(fs, ex); + exp2reg(fs, ex, var->u.info); + return; + } + case VUPVAL: { + int e = codegen_exp2anyreg(fs, ex); + codegen_codeABC(fs, OP_SETUPVAL, e, var->u.info, 0); + break; + } +#endif + case VINDEXED: { + OpCode op = (var->u.ind.vt == VLOCAL) ? OP_SETTABLE_INCR : + OP_SETTABUP_INCR; + int e = codegen_exp2RK(fs, ex); + codegen_codeABC(fs, op, var->u.ind.t, var->u.ind.idx, e); + break; + } + default: + ktap_assert(0); /* invalid var kind to store */ + break; + } + + freeexp(fs, ex); +} + + +void codegen_self(ktap_funcstate *fs, ktap_expdesc *e, ktap_expdesc *key) +{ + int ereg; + + codegen_exp2anyreg(fs, e); + ereg = e->u.info; /* register where 'e' was placed */ + freeexp(fs, e); + e->u.info = fs->freereg; /* base register for op_self */ + e->k = VNONRELOC; + codegen_reserveregs(fs, 2); /* function and 'self' produced by op_self */ + codegen_codeABC(fs, OP_SELF, e->u.info, ereg, codegen_exp2RK(fs, key)); + freeexp(fs, key); +} + +static void invertjump(ktap_funcstate *fs, ktap_expdesc *e) +{ + ktap_instruction *pc = getjumpcontrol(fs, e->u.info); + ktap_assert(testTMode(GET_OPCODE(*pc)) && GET_OPCODE(*pc) != OP_TESTSET && + GET_OPCODE(*pc) != OP_TEST); + SETARG_A(*pc, !(GETARG_A(*pc))); +} + +static int jumponcond(ktap_funcstate *fs, ktap_expdesc *e, int cond) +{ + if (e->k == VRELOCABLE) { + ktap_instruction ie = getcode(fs, e); + if (GET_OPCODE(ie) == OP_NOT) { + fs->pc--; /* remove previous OP_NOT */ + return condjump(fs, OP_TEST, GETARG_B(ie), 0, !cond); + } + /* else go through */ + } + discharge2anyreg(fs, e); + freeexp(fs, e); + return condjump(fs, OP_TESTSET, NO_REG, e->u.info, cond); +} + +void codegen_goiftrue(ktap_funcstate *fs, ktap_expdesc *e) +{ + int pc; /* pc of last jump */ + + codegen_dischargevars(fs, e); + switch (e->k) { + case VJMP: { + invertjump(fs, e); + pc = e->u.info; + break; + } + case VK: case VKNUM: case VTRUE: { + pc = NO_JUMP; /* always true; do nothing */ + break; + } + default: + pc = jumponcond(fs, e, 0); + break; + } + + codegen_concat(fs, &e->f, pc); /* insert last jump in `f' list */ + codegen_patchtohere(fs, e->t); + e->t = NO_JUMP; +} + +void codegen_goiffalse(ktap_funcstate *fs, ktap_expdesc *e) +{ + int pc; /* pc of last jump */ + codegen_dischargevars(fs, e); + + switch (e->k) { + case VJMP: { + pc = e->u.info; + break; + } + case VNIL: case VFALSE: { + pc = NO_JUMP; /* always false; do nothing */ + break; + } + default: + pc = jumponcond(fs, e, 1); + break; + } + codegen_concat(fs, &e->t, pc); /* insert last jump in `t' list */ + codegen_patchtohere(fs, e->f); + e->f = NO_JUMP; +} + +static void codenot(ktap_funcstate *fs, ktap_expdesc *e) +{ + codegen_dischargevars(fs, e); + switch (e->k) { + case VNIL: case VFALSE: { + e->k = VTRUE; + break; + } + case VK: case VKNUM: case VTRUE: { + e->k = VFALSE; + break; + } + case VJMP: { + invertjump(fs, e); + break; + } + case VRELOCABLE: + case VNONRELOC: { + discharge2anyreg(fs, e); + freeexp(fs, e); + e->u.info = codegen_codeABC(fs, OP_NOT, 0, e->u.info, 0); + e->k = VRELOCABLE; + break; + } + default: + ktap_assert(0); /* cannot happen */ + break; + } + + /* interchange true and false lists */ + { int temp = e->f; e->f = e->t; e->t = temp; } + removevalues(fs, e->f); + removevalues(fs, e->t); +} + +void codegen_indexed(ktap_funcstate *fs, ktap_expdesc *t, ktap_expdesc *k) +{ + ktap_assert(!hasjumps(t)); + t->u.ind.t = t->u.info; + t->u.ind.idx = codegen_exp2RK(fs, k); + t->u.ind.vt = (t->k == VUPVAL) ? VUPVAL + : check_exp(vkisinreg(t->k), VLOCAL); + t->k = VINDEXED; +} + +static int constfolding(OpCode op, ktap_expdesc *e1, ktap_expdesc *e2) +{ + ktap_number r; + + if (!isnumeral(e1) || !isnumeral(e2)) + return 0; + + if ((op == OP_DIV || op == OP_MOD) && e2->u.nval == 0) + return 0; /* do not attempt to divide by 0 */ + + if (op == OP_POW) + return 0; /* ktap current do not suppor pow arith */ + + r = ktapc_arith(op - OP_ADD + KTAP_OPADD, e1->u.nval, e2->u.nval); + e1->u.nval = r; + return 1; +} + +static void codearith(ktap_funcstate *fs, OpCode op, + ktap_expdesc *e1, ktap_expdesc *e2, int line) +{ + if (constfolding(op, e1, e2)) + return; + else { + int o2 = (op != OP_UNM && op != OP_LEN) ? codegen_exp2RK(fs, e2) : 0; + int o1 = codegen_exp2RK(fs, e1); + + if (o1 > o2) { + freeexp(fs, e1); + freeexp(fs, e2); + } else { + freeexp(fs, e2); + freeexp(fs, e1); + } + e1->u.info = codegen_codeABC(fs, op, 0, o1, o2); + e1->k = VRELOCABLE; + codegen_fixline(fs, line); + } +} + +static void codecomp(ktap_funcstate *fs, OpCode op, int cond, ktap_expdesc *e1, + ktap_expdesc *e2) +{ + int o1 = codegen_exp2RK(fs, e1); + int o2 = codegen_exp2RK(fs, e2); + + freeexp(fs, e2); + freeexp(fs, e1); + if (cond == 0 && op != OP_EQ) { + int temp; /* exchange args to replace by `<' or `<=' */ + temp = o1; o1 = o2; o2 = temp; /* o1 <==> o2 */ + cond = 1; + } + e1->u.info = condjump(fs, op, cond, o1, o2); + e1->k = VJMP; +} + +void codegen_prefix(ktap_funcstate *fs, UnOpr op, ktap_expdesc *e, int line) +{ + ktap_expdesc e2; + + e2.t = e2.f = NO_JUMP; + e2.k = VKNUM; + e2.u.nval = 0; + + switch (op) { + case OPR_MINUS: { + if (isnumeral(e)) /* minus constant? */ + e->u.nval = ktap_numunm(e->u.nval); /* fold it */ + else { + codegen_exp2anyreg(fs, e); + codearith(fs, OP_UNM, e, &e2, line); + } + break; + } + case OPR_NOT: + codenot(fs, e); + break; + case OPR_LEN: { + codegen_exp2anyreg(fs, e); /* cannot operate on constants */ + codearith(fs, OP_LEN, e, &e2, line); + break; + } + default: + ktap_assert(0); + } +} + +void codegen_infix(ktap_funcstate *fs, BinOpr op, ktap_expdesc *v) +{ + switch (op) { + case OPR_AND: { + codegen_goiftrue(fs, v); + break; + } + case OPR_OR: { + codegen_goiffalse(fs, v); + break; + } + case OPR_CONCAT: { + codegen_exp2nextreg(fs, v); /* operand must be on the `stack' */ + break; + } + case OPR_ADD: case OPR_SUB: case OPR_MUL: case OPR_DIV: + case OPR_MOD: case OPR_POW: { + if (!isnumeral(v)) codegen_exp2RK(fs, v); + break; + } + default: + codegen_exp2RK(fs, v); + break; + } +} + +void codegen_posfix(ktap_funcstate *fs, BinOpr op, ktap_expdesc *e1, ktap_expdesc *e2, int line) +{ + switch (op) { + case OPR_AND: { + ktap_assert(e1->t == NO_JUMP); /* list must be closed */ + codegen_dischargevars(fs, e2); + codegen_concat(fs, &e2->f, e1->f); + *e1 = *e2; + break; + } + case OPR_OR: { + ktap_assert(e1->f == NO_JUMP); /* list must be closed */ + codegen_dischargevars(fs, e2); + codegen_concat(fs, &e2->t, e1->t); + *e1 = *e2; + break; + } + case OPR_CONCAT: { + codegen_exp2val(fs, e2); + if (e2->k == VRELOCABLE && GET_OPCODE(getcode(fs, e2)) == OP_CONCAT) { + ktap_assert(e1->u.info == GETARG_B(getcode(fs, e2))-1); + freeexp(fs, e1); + SETARG_B(getcode(fs, e2), e1->u.info); + e1->k = VRELOCABLE; e1->u.info = e2->u.info; + } else { + codegen_exp2nextreg(fs, e2); /* operand must be on the 'stack' */ + codearith(fs, OP_CONCAT, e1, e2, line); + } + break; + } + case OPR_ADD: case OPR_SUB: case OPR_MUL: case OPR_DIV: + case OPR_MOD: case OPR_POW: { + codearith(fs, (OpCode)(op - OPR_ADD + OP_ADD), e1, e2, line); + break; + } + case OPR_EQ: case OPR_LT: case OPR_LE: { + codecomp(fs, (OpCode)(op - OPR_EQ + OP_EQ), 1, e1, e2); + break; + } + case OPR_NE: case OPR_GT: case OPR_GE: { + codecomp(fs, (OpCode)(op - OPR_NE + OP_EQ), 0, e1, e2); + break; + } + default: + ktap_assert(0); + } +} + +void codegen_fixline(ktap_funcstate *fs, int line) +{ + fs->f->lineinfo[fs->pc - 1] = line; +} + +void codegen_setlist(ktap_funcstate *fs, int base, int nelems, int tostore) +{ + int c = (nelems - 1)/LFIELDS_PER_FLUSH + 1; + int b = (tostore == KTAP_MULTRET) ? 0 : tostore; + + ktap_assert(tostore != 0); + if (c <= MAXARG_C) + codegen_codeABC(fs, OP_SETLIST, base, b, c); + else if (c <= MAXARG_Ax) { + codegen_codeABC(fs, OP_SETLIST, base, b, 0); + codeextraarg(fs, c); + } else + lex_syntaxerror(fs->ls, "constructor too long"); + fs->freereg = base + 1; /* free registers with list values */ +} + diff --git a/drivers/staging/ktap/userspace/dump.c b/drivers/staging/ktap/userspace/dump.c new file mode 100644 index 000000000000..088b08d0fa8e --- /dev/null +++ b/drivers/staging/ktap/userspace/dump.c @@ -0,0 +1,187 @@ +/* + * dump.c - save precompiled ktap chunks + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * Copyright (C) 1994-2013 Lua.org, PUC-Rio. + * - The part of code in this file is copied from lua initially. + * - lua's MIT license is compatible with GPL. + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +#include "../include/ktap_types.h" +#include "../include/ktap_opcodes.h" +#include "ktapc.h" + + +typedef struct { + ktap_writer writer; + void *data; + int strip; + int status; +} DumpState; + +#define DumpMem(b, n, size, D) DumpBlock(b, (n)*(size), D) +#define DumpVar(x, D) DumpMem(&x, 1, sizeof(x), D) + +static void DumpBlock(const void *b, size_t size, DumpState *D) +{ + if (D->status == 0) + D->status = ((D->writer))(b, size, D->data); +} + +static void DumpChar(int y, DumpState *D) +{ + char x = (char)y; + DumpVar(x, D); +} + +static void DumpInt(int x, DumpState *D) +{ + DumpVar(x, D); +} + +static void DumpNumber(ktap_number x, DumpState *D) +{ + DumpVar(x,D); +} + +static void DumpVector(const void *b, int n, size_t size, DumpState *D) +{ + DumpInt(n, D); + DumpMem(b, n, size, D); +} + +static void DumpString(const ktap_string *s, DumpState *D) +{ + if (s == NULL) { + int size = 0; + DumpVar(size, D); + } else { + int size = s->tsv.len + 1; /* include trailing '\0' */ + DumpVar(size, D); + DumpBlock(getstr(s), size * sizeof(char), D); + } +} + +#define DumpCode(f, D) DumpVector(f->code, f->sizecode, sizeof(ktap_instruction), D) + +static void DumpFunction(const ktap_proto *f, DumpState *D); + +static void DumpConstants(const ktap_proto *f, DumpState *D) +{ + int i, n = f->sizek; + + DumpInt(n, D); + for (i = 0; i < n; i++) { + const ktap_value* o=&f->k[i]; + DumpChar(ttypenv(o), D); + switch (ttypenv(o)) { + case KTAP_TNIL: + break; + case KTAP_TBOOLEAN: + DumpChar(bvalue(o), D); + break; + case KTAP_TNUMBER: + DumpNumber(nvalue(o), D); + break; + case KTAP_TSTRING: + DumpString(rawtsvalue(o), D); + break; + default: + printf("ktap: DumpConstants with unknown vaule type %d\n", ttypenv(o)); + ktap_assert(0); + } + } + n = f->sizep; + DumpInt(n, D); + for (i = 0; i < n; i++) + DumpFunction(f->p[i], D); +} + +static void DumpUpvalues(const ktap_proto *f, DumpState *D) +{ + int i, n = f->sizeupvalues; + + DumpInt(n, D); + for (i = 0; i < n; i++) { + DumpChar(f->upvalues[i].instack, D); + DumpChar(f->upvalues[i].idx, D); + } +} + +static void DumpDebug(const ktap_proto *f, DumpState *D) +{ + int i,n; + + DumpString((D->strip) ? NULL : f->source, D); + n= (D->strip) ? 0 : f->sizelineinfo; + DumpVector(f->lineinfo, n, sizeof(int), D); + n = (D->strip) ? 0 : f->sizelocvars; + DumpInt(n, D); + + for (i = 0; i < n; i++) { + DumpString(f->locvars[i].varname, D); + DumpInt(f->locvars[i].startpc, D); + DumpInt(f->locvars[i].endpc, D); + } + n = (D->strip) ? 0 : f->sizeupvalues; + DumpInt(n, D); + for (i = 0; i < n; i++) + DumpString(f->upvalues[i].name, D); +} + +static void DumpFunction(const ktap_proto *f, DumpState *D) +{ + DumpInt(f->linedefined, D); + DumpInt(f->lastlinedefined, D); + DumpChar(f->numparams, D); + DumpChar(f->is_vararg, D); + DumpChar(f->maxstacksize, D); + DumpCode(f, D); + DumpConstants(f, D); + DumpUpvalues(f, D); + DumpDebug(f, D); +} + +static void DumpHeader(DumpState *D) +{ + u8 h[KTAPC_HEADERSIZE]; + + kp_header(h); + DumpBlock(h, KTAPC_HEADERSIZE, D); +} + +/* + * dump ktap function as precompiled chunk + */ +int ktapc_dump(const ktap_proto *f, ktap_writer w, void *data, int strip) +{ + DumpState D; + + D.writer = w; + D.data = data; + D.strip = strip; + D.status = 0; + DumpHeader(&D); + DumpFunction(f, &D); + return D.status; +} diff --git a/drivers/staging/ktap/userspace/eventdef.c b/drivers/staging/ktap/userspace/eventdef.c new file mode 100644 index 000000000000..76a68ac8aabc --- /dev/null +++ b/drivers/staging/ktap/userspace/eventdef.c @@ -0,0 +1,588 @@ +/* + * eventdef.c - ktap eventdef parser + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "../include/ktap_types.h" +#include "../include/ktap_opcodes.h" +#include "ktapc.h" + +static char tracing_events_path[] = "/sys/kernel/debug/tracing/events"; + +#define IDS_ARRAY_SIZE 4096 +static u8 *ids_array; + +#define set_id(id) \ + do { \ + ids_array[id/8] = ids_array[id/8] | (1 << (id%8)); \ + } while(0) + +#define clear_id(id) \ + do { \ + ids_array[id/8] = ids_array[id/8] & ~ (1 << (id%8)); \ + } while(0) + + +static int get_digit_len(int id) +{ + int len = -1; + + if (id < 10) + len = 1; + else if (id < 100) + len = 2; + else if (id < 1000) + len = 3; + else if (id < 10000) + len = 4; + else if (id < 100000) + len = 5; + + return len; +} + +static char *get_idstr(char *filter) +{ + char *idstr, *ptr; + int total_len = 0; + int filter_len; + int i; + + filter_len = filter ? strlen(filter) : 0; + + for (i = 0; i < IDS_ARRAY_SIZE*8; i++) { + if (ids_array[i/8] & (1 << (i%8))) + total_len += get_digit_len(i) + 1; + } + + if (!total_len) + return NULL; + + idstr = malloc(total_len + filter_len + 1); + if (!idstr) + return NULL; + + memset(idstr, 0, total_len + filter_len + 1); + ptr = idstr; + for (i = 0; i < IDS_ARRAY_SIZE*8; i++) { + if (ids_array[i/8] & (1 << (i%8))) { + char digits[32] = {0}; + int len; + + sprintf(digits, "%d ", i); + len = strlen(digits); + strncpy(ptr, digits, len); + ptr += len; + } + } + + if (filter) + memcpy(ptr, filter, strlen(filter)); + + return idstr; +} + +static int add_event(char *evtid_path) +{ + char id_buf[24]; + int id, fd; + + fd = open(evtid_path, O_RDONLY); + if (fd < 0) { + /* + * some tracepoint doesn't have id file, like ftrace, + * return success in here, and don't print error. + */ + verbose_printf("warning: cannot open file %s\n", evtid_path); + return 0; + } + + if (read(fd, id_buf, sizeof(id_buf)) < 0) { + fprintf(stderr, "read file error %s\n", evtid_path); + close(fd); + return -1; + } + + id = atoll(id_buf); + + if (id >= IDS_ARRAY_SIZE * 8) { + fprintf(stderr, "tracepoint id(%d) is bigger than %d\n", id, + IDS_ARRAY_SIZE * 8); + close(fd); + return -1; + } + + set_id(id); + + close(fd); + return 0; +} + +static int add_tracepoint(char *sys_name, char *evt_name) +{ + char evtid_path[PATH_MAX] = {0}; + + + snprintf(evtid_path, PATH_MAX, "%s/%s/%s/id", tracing_events_path, + sys_name, evt_name); + return add_event(evtid_path); +} + +static int add_tracepoint_multi_event(char *sys_name, char *evt_name) +{ + char evt_path[PATH_MAX]; + struct dirent *evt_ent; + DIR *evt_dir; + int ret = 0; + + snprintf(evt_path, PATH_MAX, "%s/%s", tracing_events_path, sys_name); + evt_dir = opendir(evt_path); + if (!evt_dir) { + perror("Can't open event dir"); + return -1; + } + + while (!ret && (evt_ent = readdir(evt_dir))) { + if (!strcmp(evt_ent->d_name, ".") + || !strcmp(evt_ent->d_name, "..") + || !strcmp(evt_ent->d_name, "enable") + || !strcmp(evt_ent->d_name, "filter")) + continue; + + if (!strglobmatch(evt_ent->d_name, evt_name)) + continue; + + ret = add_tracepoint(sys_name, evt_ent->d_name); + } + + closedir(evt_dir); + return ret; +} + +static int add_tracepoint_event(char *sys_name, char *evt_name) +{ + return strpbrk(evt_name, "*?") ? + add_tracepoint_multi_event(sys_name, evt_name) : + add_tracepoint(sys_name, evt_name); +} + +static int add_tracepoint_multi_sys(char *sys_name, char *evt_name) +{ + struct dirent *events_ent; + DIR *events_dir; + int ret = 0; + + events_dir = opendir(tracing_events_path); + if (!events_dir) { + perror("Can't open event dir"); + return -1; + } + + while (!ret && (events_ent = readdir(events_dir))) { + if (!strcmp(events_ent->d_name, ".") + || !strcmp(events_ent->d_name, "..") + || !strcmp(events_ent->d_name, "enable") + || !strcmp(events_ent->d_name, "header_event") + || !strcmp(events_ent->d_name, "header_page")) + continue; + + if (!strglobmatch(events_ent->d_name, sys_name)) + continue; + + ret = add_tracepoint_event(events_ent->d_name, + evt_name); + } + + closedir(events_dir); + return ret; +} + +static int parse_events_add_tracepoint(char *sys, char *event) +{ + if (strpbrk(sys, "*?")) + return add_tracepoint_multi_sys(sys, event); + else + return add_tracepoint_event(sys, event); +} + +enum { + KPROBE_EVENT, + UPROBE_EVENT, +}; + +struct probe_list { + struct probe_list *next; + int type; + int kp_seq; + char *probe_event; +}; + +static struct probe_list *probe_list_head; + +#define KPROBE_EVENTS_PATH "/sys/kernel/debug/tracing/kprobe_events" + +static int parse_events_add_kprobe(char *old_event) +{ + static int event_seq = 0; + struct probe_list *pl; + char probe_event[128] = {0}; + char event_id_path[128] = {0}; + char *event; + char *r; + int fd; + int ret; + + fd = open(KPROBE_EVENTS_PATH, O_WRONLY); + if (fd < 0) { + fprintf(stderr, "Cannot open %s\n", KPROBE_EVENTS_PATH); + return -1; + } + + event = strdup(old_event); + r = strstr(event, "%return"); + if (r) { + memset(r, ' ', 7); + snprintf(probe_event, 128, "r:kprobes/kp%d %s", + event_seq, event); + } else + snprintf(probe_event, 128, "p:kprobes/kp%d %s", + event_seq, event); + + free(event); + + verbose_printf("kprobe event %s\n", probe_event); + ret = write(fd, probe_event, strlen(probe_event)); + if (ret <= 0) { + fprintf(stderr, "Cannot write %s to %s\n", probe_event, + KPROBE_EVENTS_PATH); + close(fd); + return -1; + } + + close(fd); + + pl = malloc(sizeof(struct probe_list)); + if (!pl) + return -1; + + pl->type = KPROBE_EVENT; + pl->kp_seq = event_seq; + pl->next = probe_list_head; + probe_list_head = pl; + + sprintf(event_id_path, "/sys/kernel/debug/tracing/events/kprobes/kp%d/id", + event_seq); + ret = add_event(event_id_path); + if (ret < 0) + return -1; + + event_seq++; + return 0; +} + +#define UPROBE_EVENTS_PATH "/sys/kernel/debug/tracing/uprobe_events" + +static int parse_events_add_uprobe(char *old_event) +{ + static int event_seq = 0; + struct probe_list *pl; + char probe_event[128] = {0}; + char event_id_path[128] = {0}; + char *event; + char *r; + int fd; + int ret; + + fd = open(UPROBE_EVENTS_PATH, O_WRONLY); + if (fd < 0) { + fprintf(stderr, "Cannot open %s\n", UPROBE_EVENTS_PATH); + return -1; + } + + event = strdup(old_event); + r = strstr(event, "%return"); + if (r) { + memset(r, ' ', 7); + snprintf(probe_event, 128, "r:uprobes/kp%d %s", + event_seq, event); + } else + snprintf(probe_event, 128, "p:uprobes/kp%d %s", + event_seq, event); + + free(event); + + verbose_printf("uprobe event %s\n", probe_event); + ret = write(fd, probe_event, strlen(probe_event)); + if (ret <= 0) { + fprintf(stderr, "Cannot write %s to %s\n", probe_event, + UPROBE_EVENTS_PATH); + close(fd); + return -1; + } + + close(fd); + + pl = malloc(sizeof(struct probe_list)); + if (!pl) + return -1; + + pl->type = UPROBE_EVENT; + pl->kp_seq = event_seq; + pl->next = probe_list_head; + probe_list_head = pl; + + sprintf(event_id_path, "/sys/kernel/debug/tracing/events/uprobes/kp%d/id", + event_seq); + ret = add_event(event_id_path); + if (ret < 0) + return -1; + + event_seq++; + return 0; +} + +static int parse_events_add_probe(char *old_event) +{ + char *separator; + + separator = strchr(old_event, ':'); + if (!separator || (separator == old_event)) + return parse_events_add_kprobe(old_event); + else + return parse_events_add_uprobe(old_event); +} + +static int parse_events_add_stapsdt(char *old_event) +{ + printf("Currently ktap don't support stapsdt, please waiting\n"); + + return -1; +} + +static void strim(char *s) +{ + size_t size; + char *end; + + size = strlen(s); + if (!size) + return; + + end = s + size -1; + while (end >= s && isspace(*end)) + end--; + + *(end + 1) = '\0'; +} + +static int get_sys_event_filter_str(char *start, + char **sys, char **event, char **filter) +{ + char *separator, *separator2, *ptr, *end; + + while (*start == ' ') + start++; + + /* find sys */ + separator = strchr(start, ':'); + if (!separator || (separator == start)) { + return -1; + } + + ptr = malloc(separator - start + 1); + if (!ptr) + return -1; + + strncpy(ptr, start, separator - start); + ptr[separator - start] = '\0'; + + strim(ptr); + *sys = ptr; + + if (!strcmp(*sys, "probe") && (*(separator + 1) == '/')) { + /* it's uprobe event */ + separator2 = strchr(separator + 1, ':'); + if (!separator2) + return -1; + } else + separator2 = separator; + + /* find filter */ + end = start + strlen(start); + while (*--end == ' ') { + } + + if (*end == '/') { + char *filter_start; + + filter_start = strchr(separator2, '/'); + if (filter_start == end) + return -1; + + ptr = malloc(end - filter_start + 2); + if (!ptr) + return -1; + + memcpy(ptr, filter_start, end - filter_start + 1); + ptr[end - filter_start + 1] = '\0'; + + *filter = ptr; + + end = filter_start; + } else { + *filter = NULL; + end++; + } + + /* find event */ + ptr = malloc(end - separator); + if (!ptr) + return -1; + + memcpy(ptr, separator + 1, end - separator - 1); + ptr[end - separator - 1] = '\0'; + + strim(ptr); + *event = ptr; + + return 0; +} + +static char *get_next_eventdef(char *str) +{ + char *separator; + + separator = strchr(str, ','); + if (!separator) + return str + strlen(str); + + *separator = '\0'; + return separator + 1; +} + +ktap_string *ktapc_parse_eventdef(ktap_string *eventdef) +{ + const char *def_str = getstr(eventdef); + char *str = strdup(def_str); + char *sys, *event, *filter, *idstr, *g_idstr, *next; + ktap_string *ts; + int ret; + + if (!ids_array) { + ids_array = malloc(IDS_ARRAY_SIZE); + if (!ids_array) + return NULL; + } + + g_idstr = malloc(4096); + if (!g_idstr) + return NULL; + + memset(g_idstr, 0, 4096); + + parse_next_eventdef: + memset(ids_array, 0, IDS_ARRAY_SIZE); + + next = get_next_eventdef(str); + + if (get_sys_event_filter_str(str, &sys, &event, &filter)) + goto error; + + verbose_printf("parse_eventdef: sys[%s], event[%s], filter[%s]\n", + sys, event, filter); + + if (!strcmp(sys, "probe")) + ret = parse_events_add_probe(event); + else if (!strcmp(sys, "stapsdt")) + ret = parse_events_add_stapsdt(event); + else + ret = parse_events_add_tracepoint(sys, event); + + if (ret) + goto error; + + /* don't trace ftrace:function when all tracepoints enabled */ + if (!strcmp(sys, "*")) + clear_id(1); + + idstr = get_idstr(filter); + if (!idstr) + goto error; + + str = next; + + g_idstr = strcat(g_idstr, idstr); + g_idstr = strcat(g_idstr, ","); + + if (*next != '\0') + goto parse_next_eventdef; + + ts = ktapc_ts_new(g_idstr); + free(g_idstr); + + return ts; + error: + cleanup_event_resources(); + return NULL; +} + +void cleanup_event_resources(void) +{ + struct probe_list *pl; + const char *path; + char probe_event[32] = {0}; + int fd, ret; + + for (pl = probe_list_head; pl; pl = pl->next) { + if (pl->type == KPROBE_EVENT) { + path = KPROBE_EVENTS_PATH; + snprintf(probe_event, 32, "-:kprobes/kp%d", pl->kp_seq); + } else if (pl->type == UPROBE_EVENT) { + path = UPROBE_EVENTS_PATH; + snprintf(probe_event, 32, "-:uprobes/kp%d", pl->kp_seq); + } else { + fprintf(stderr, "Cannot cleanup event type %d\n", pl->type); + continue; + } + + fd = open(path, O_WRONLY); + if (fd < 0) { + fprintf(stderr, "Cannot open %s\n", UPROBE_EVENTS_PATH); + continue; + } + + ret = write(fd, probe_event, strlen(probe_event)); + if (ret <= 0) { + fprintf(stderr, "Cannot write %s to %s\n", probe_event, + path); + close(fd); + continue; + } + + close(fd); + } +} + diff --git a/drivers/staging/ktap/userspace/ktapc.h b/drivers/staging/ktap/userspace/ktapc.h new file mode 100644 index 000000000000..879cd29d8c94 --- /dev/null +++ b/drivers/staging/ktap/userspace/ktapc.h @@ -0,0 +1,376 @@ +/* + * ktapc.h + * only can be included by userspace compiler + */ + +#include + +typedef int bool; +#define false 0 +#define true 1 + +#define MAX_INT ((int)(~0U>>1)) +#define UCHAR_MAX 255 + +#define MAX_SIZET ((size_t)(~(size_t)0)-2) + +#define KTAP_ERRSYNTAX 3 + +/* + * KTAP_IDSIZE gives the maximum size for the description of the source + * of a function in debug information. + * CHANGE it if you want a different size. + */ +#define KTAP_IDSIZE 60 + + +#define FIRST_RESERVED 257 + +/* + * maximum depth for nested C calls and syntactical nested non-terminals + * in a program. (Value must fit in an unsigned short int.) + */ +#define KTAP_MAXCCALLS 200 + +#define KTAP_MULTRET (-1) + + +#define SHRT_MAX UCHAR_MAX + +#define MAXUPVAL UCHAR_MAX + + +/* maximum stack for a ktap function */ +#define MAXSTACK 250 + +#define islalpha(c) (isalpha(c) || (c) == '_') +#define islalnum(c) (isalnum(c) || (c) == '_') + +#define isreserved(s) ((s)->tsv.tt == KTAP_TSHRSTR && (s)->tsv.extra > 0) + +#define ktap_numeq(a,b) ((a)==(b)) +#define ktap_numisnan(L,a) (!ktap_numeq((a), (a))) + +#define ktap_numunm(a) (-(a)) + +/* + * ** Comparison and arithmetic functions + * */ + +#define KTAP_OPADD 0 /* ORDER TM */ +#define KTAP_OPSUB 1 +#define KTAP_OPMUL 2 +#define KTAP_OPDIV 3 +#define KTAP_OPMOD 4 +#define KTAP_OPPOW 5 +#define KTAP_OPUNM 6 + +#define KTAP_OPEQ 0 +#define KTAP_OPLT 1 +#define KTAP_OPLE 2 + + +/* + * WARNING: if you change the order of this enumeration, + * grep "ORDER RESERVED" + */ +enum RESERVED { + /* terminal symbols denoted by reserved words */ + TK_TRACE = FIRST_RESERVED, TK_TRACE_END, + TK_ARGEVENT, TK_ARGNAME, + TK_ARG1, TK_ARG2, TK_ARG3, TK_ARG4, TK_ARG5, TK_ARG6, TK_ARG7, TK_ARG8, + TK_ARG9, TK_PROFILE, TK_TICK, + TK_AND, TK_BREAK, + TK_DO, TK_ELSE, TK_ELSEIF, TK_END, TK_FALSE, TK_FOR, TK_FUNCTION, + TK_GOTO, TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, TK_REPEAT, + TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE, + /* other terminal symbols */ + TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE, TK_INCR, TK_DBCOLON, + TK_EOS, TK_NUMBER, TK_NAME, TK_STRING +}; + +/* number of reserved words */ +#define NUM_RESERVED ((int)(TK_WHILE-FIRST_RESERVED + 1)) + +#define EOZ (0) /* end of stream */ + +typedef union { + ktap_number r; + ktap_string *ts; +} ktap_seminfo; /* semantics information */ + + +typedef struct ktap_token { + int token; + ktap_seminfo seminfo; +} ktap_token; + +typedef struct ktap_mbuffer { + char *buffer; + size_t n; + size_t buffsize; +} ktap_mbuffer; + +#define mbuff_init(buff) ((buff)->buffer = NULL, (buff)->buffsize = 0) +#define mbuff(buff) ((buff)->buffer) +#define mbuff_reset(buff) ((buff)->n = 0, memset((buff)->buffer, 0, (buff)->buffsize)) +#define mbuff_len(buff) ((buff)->n) +#define mbuff_size(buff) ((buff)->buffsize) + +#define mbuff_resize(buff, size) \ + (ktapc_realloc((buff)->buffer, (buff)->buffsize, size, char), \ + (buff)->buffsize = size) + +#define mbuff_free(buff) mbuff_resize(buff, 0) + + +/* + * state of the lexer plus state of the parser when shared by all + * functions + */ +typedef struct ktap_lexstate { + char *ptr; /* source file reading position */ + int current; /* current character (charint) */ + int linenumber; /* input line counter */ + int lastline; /* line of last token `consumed' */ + ktap_token t; /* current token */ + ktap_token lookahead; /* look ahead token */ + struct ktap_funcstate *fs; /* current function (parser) */ + ktap_mbuffer *buff; /* buffer for tokens */ + struct ktap_dyndata *dyd; /* dynamic structures used by the parser */ + ktap_string *source; /* current source name */ + ktap_string *envn; /* environment variable name */ + char decpoint; /* locale decimal point */ + int nCcalls; +} ktap_lexstate; + + +/* + * Expression descriptor + */ +typedef enum { + VVOID, /* no value */ + VNIL, + VTRUE, + VFALSE, + VK, /* info = index of constant in `k' */ + VKNUM, /* nval = numerical value */ + VNONRELOC, /* info = result register */ + VLOCAL, /* info = local register */ + VUPVAL, /* info = index of upvalue in 'upvalues' */ + VINDEXED, /* t = table register/upvalue; idx = index R/K */ + VJMP, /* info = instruction pc */ + VRELOCABLE, /* info = instruction pc */ + VCALL, /* info = instruction pc */ + VVARARG, /* info = instruction pc */ + VEVENT, + VEVENTNAME, + VEVENTARG, +} expkind; + + +#define vkisvar(k) (VLOCAL <= (k) && (k) <= VINDEXED) +#define vkisinreg(k) ((k) == VNONRELOC || (k) == VLOCAL) + +typedef struct ktap_expdesc { + expkind k; + union { + struct { /* for indexed variables (VINDEXED) */ + short idx; /* index (R/K) */ + u8 t; /* table (register or upvalue) */ + u8 vt; /* whether 't' is register (VLOCAL) or upvalue (VUPVAL) */ + } ind; + int info; /* for generic use */ + ktap_number nval; /* for VKNUM */ + } u; + int t; /* patch list of `exit when true' */ + int f; /* patch list of `exit when false' */ +} ktap_expdesc; + + +typedef struct ktap_vardesc { + short idx; /* variable index in stack */ +} ktap_vardesc; + + +/* description of pending goto statements and label statements */ +typedef struct ktap_labeldesc { + ktap_string *name; /* label identifier */ + int pc; /* position in code */ + int line; /* line where it appeared */ + u8 nactvar; /* local level where it appears in current block */ +} ktap_labeldesc; + + +/* list of labels or gotos */ +typedef struct ktap_labellist { + ktap_labeldesc *arr; /* array */ + int n; /* number of entries in use */ + int size; /* array size */ +} ktap_labellist; + + +/* dynamic structures used by the parser */ +typedef struct ktap_dyndata { + struct { /* list of active local variables */ + ktap_vardesc *arr; + int n; + int size; + } actvar; + ktap_labellist gt; /* list of pending gotos */ + ktap_labellist label; /* list of active labels */ +} ktap_dyndata; + + +/* control of blocks */ +struct ktap_blockcnt; /* defined in lparser.c */ + + +/* state needed to generate code for a given function */ +typedef struct ktap_funcstate { + ktap_proto *f; /* current function header */ + ktap_table *h; /* table to find (and reuse) elements in `k' */ + struct ktap_funcstate *prev; /* enclosing function */ + struct ktap_lexstate *ls; /* lexical state */ + struct ktap_blockcnt *bl; /* chain of current blocks */ + int pc; /* next position to code (equivalent to `ncode') */ + int lasttarget; /* 'label' of last 'jump label' */ + int jpc; /* list of pending jumps to `pc' */ + int nk; /* number of elements in `k' */ + int np; /* number of elements in `p' */ + int firstlocal; /* index of first local var (in ktap_dyndata array) */ + short nlocvars; /* number of elements in 'f->locvars' */ + u8 nactvar; /* number of active local variables */ + u8 nups; /* number of upvalues */ + u8 freereg; /* first free register */ +} ktap_funcstate; + + +/* + * Marks the end of a patch list. It is an invalid value both as an absolute + * address, and as a list link (would link an element to itself). + */ +#define NO_JUMP (-1) + + +/* + * grep "ORDER OPR" if you change these enums (ORDER OP) + */ +typedef enum BinOpr { + OPR_ADD, OPR_SUB, OPR_MUL, OPR_DIV, OPR_MOD, OPR_POW, + OPR_CONCAT, + OPR_EQ, OPR_LT, OPR_LE, + OPR_NE, OPR_GT, OPR_GE, + OPR_AND, OPR_OR, + OPR_NOBINOPR +} BinOpr; + + +typedef enum UnOpr { OPR_MINUS, OPR_NOT, OPR_LEN, OPR_NOUNOPR } UnOpr; + + +#define getcode(fs,e) ((fs)->f->code[(e)->u.info]) + +#define codegen_codeAsBx(fs,o,A,sBx) codegen_codeABx(fs,o,A,(sBx)+MAXARG_sBx) + +#define codegen_setmultret(fs,e) codegen_setreturns(fs, e, KTAP_MULTRET) + +#define codegen_jumpto(fs,t) codegen_patchlist(fs, codegen_jump(fs), t) + + +#define ktapc_realloc(v, osize, nsize, t) \ + ((v) = (t *)ktapc_reallocv(v, osize * sizeof(t), nsize * sizeof(t))) + +#define ktapc_reallocvector(v,oldn,n,t) ktapc_realloc(v,oldn,n,t) + + +#define ktapc_growvector(v,nelems,size,t,limit,e) \ + if ((nelems)+1 > (size)) \ + ((v)=(t *)ktapc_growaux(v,&(size),sizeof(t),limit,e)) + + +void lex_init(); +ktap_string *lex_newstring(ktap_lexstate *ls, const char *str, size_t l); +const char *lex_token2str(ktap_lexstate *ls, int token); +void lex_syntaxerror(ktap_lexstate *ls, const char *msg); +void lex_setinput(ktap_lexstate *ls, char *ptr, ktap_string *source, int firstchar); +void lex_next(ktap_lexstate *ls); +int lex_lookahead(ktap_lexstate *ls); +void lex_read_string_until(ktap_lexstate *ls, int c); +ktap_closure *ktapc_parser(char *pos, const char *name); +ktap_string *ktapc_ts_new(const char *str); +int ktapc_ts_eqstr(ktap_string *a, ktap_string *b); +ktap_string *ktapc_ts_newlstr(const char *str, size_t l); +ktap_proto *ktapc_newproto(); +ktap_table *ktapc_table_new(); +const ktap_value *ktapc_table_get(ktap_table *t, const ktap_value *key); +void ktapc_table_setvalue(ktap_table *t, const ktap_value *key, ktap_value *val); +ktap_closure *ktapc_newlclosure(int n); +char *ktapc_sprintf(const char *fmt, ...); + +void *ktapc_reallocv(void *block, size_t osize, size_t nsize); +void *ktapc_growaux(void *block, int *size, size_t size_elems, int limit, + const char *what); + +void ktapio_exit(void); +int ktapio_create(const char *output_filename); + +ktap_string *ktapc_parse_eventdef(ktap_string *eventdef); +void cleanup_event_resources(void); + +extern int verbose; +#define verbose_printf(...) \ + if (verbose) \ + printf("[verbose] " __VA_ARGS__); + +#define ktapc_equalobj(t1, t2) kp_equalobjv(NULL, t1, t2) + + +#include "../include/ktap_opcodes.h" + +int codegen_stringK(ktap_funcstate *fs, ktap_string *s); +void codegen_indexed(ktap_funcstate *fs, ktap_expdesc *t, ktap_expdesc *k); +void codegen_setreturns(ktap_funcstate *fs, ktap_expdesc *e, int nresults); +void codegen_reserveregs(ktap_funcstate *fs, int n); +void codegen_exp2nextreg(ktap_funcstate *fs, ktap_expdesc *e); +void codegen_nil(ktap_funcstate *fs, int from, int n); +void codegen_patchlist(ktap_funcstate *fs, int list, int target); +void codegen_patchclose(ktap_funcstate *fs, int list, int level); +int codegen_jump(ktap_funcstate *fs); +void codegen_patchtohere(ktap_funcstate *fs, int list); +int codegen_codeABx(ktap_funcstate *fs, OpCode o, int a, unsigned int bc); +void codegen_ret(ktap_funcstate *fs, int first, int nret); +void codegen_exp2anyregup(ktap_funcstate *fs, ktap_expdesc *e); +void codegen_exp2val(ktap_funcstate *fs, ktap_expdesc *e); +int codegen_exp2RK(ktap_funcstate *fs, ktap_expdesc *e); +int codegen_codeABC(ktap_funcstate *fs, OpCode o, int a, int b, int c); +void codegen_setlist(ktap_funcstate *fs, int base, int nelems, int tostore); +void codegen_fixline (ktap_funcstate *fs, int line); +void codegen_dischargevars(ktap_funcstate *fs, ktap_expdesc *e); +void codegen_self(ktap_funcstate *fs, ktap_expdesc *e, ktap_expdesc *key); +void codegen_prefix(ktap_funcstate *fs, UnOpr op, ktap_expdesc *e, int line); +void codegen_infix(ktap_funcstate *fs, BinOpr op, ktap_expdesc *v); +void codegen_posfix(ktap_funcstate *fs, BinOpr op, ktap_expdesc *e1, ktap_expdesc *e2, int line); +void codegen_setoneret(ktap_funcstate *fs, ktap_expdesc *e); +void codegen_storevar(ktap_funcstate *fs, ktap_expdesc *var, ktap_expdesc *ex); +void codegen_storeincr(ktap_funcstate *fs, ktap_expdesc *var, ktap_expdesc *ex); +void codegen_goiftrue(ktap_funcstate *fs, ktap_expdesc *e); +int codegen_getlabel(ktap_funcstate *fs); +int codegen_codek(ktap_funcstate *fs, int reg, int k); +int codegen_numberK(ktap_funcstate *fs, ktap_number r); +void codegen_checkstack(ktap_funcstate *fs, int n); +void codegen_goiffalse(ktap_funcstate *fs, ktap_expdesc *e); +void codegen_concat(ktap_funcstate *fs, int *l1, int l2); +int codegen_exp2anyreg(ktap_funcstate *fs, ktap_expdesc *e); + +typedef int (*ktap_writer)(const void* p, size_t sz, void* ud); +int ktapc_dump(const ktap_proto *f, ktap_writer w, void *data, int strip); + +void ktapc_chunkid(char *out, const char *source, size_t bufflen); +int ktapc_str2d(const char *s, size_t len, ktap_number *result); +int ktapc_hexavalue(int c); +ktap_number ktapc_arith(int op, ktap_number v1, ktap_number v2); +int ktapc_int2fb(unsigned int x); + +bool strglobmatch(const char *str, const char *pat); + diff --git a/drivers/staging/ktap/userspace/ktapio.c b/drivers/staging/ktap/userspace/ktapio.c new file mode 100644 index 000000000000..f9bcab461f2a --- /dev/null +++ b/drivers/staging/ktap/userspace/ktapio.c @@ -0,0 +1,108 @@ +/* + * ktapio.c - ring buffer transport in userspace + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_BUFLEN 131072 +#define PATH_MAX 128 + +#define handle_error(str) do { perror(str); exit(-1); } while(0) + +extern pid_t ktap_pid; + +void sigfunc(int signo) +{ + /* should not not reach here */ +} + +static void block_sigint() +{ + sigset_t mask; + + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + + pthread_sigmask(SIG_BLOCK, &mask, NULL); +} + +static void *reader_thread(void *data) +{ + char buf[MAX_BUFLEN]; + char filename[PATH_MAX]; + const char *output = data; + int failed = 0, fd, out_fd, len; + + block_sigint(); + + if (output) { + out_fd = open(output, O_CREAT | O_WRONLY | O_TRUNC, + S_IRUSR|S_IWUSR); + if (out_fd < 0) { + fprintf(stderr, "Cannot open output file %s\n", output); + return NULL; + } + } else + out_fd = 2; + + sprintf(filename, "/sys/kernel/debug/ktap/trace_pipe_%d", ktap_pid); + + open_again: + fd = open(filename, O_RDONLY); + if (fd < 0) { + usleep(10000); + + if (failed++ == 10) { + fprintf(stderr, "Cannot open file %s\n", filename); + return NULL; + } + goto open_again; + } + + while ((len = read(fd, buf, sizeof(buf))) > 0) + write(out_fd, buf, len); + + close(fd); + close(out_fd); + + return NULL; +} + +int ktapio_create(const char *output) +{ + pthread_t reader; + + signal(SIGINT, sigfunc); + + if (pthread_create(&reader, NULL, reader_thread, (void *)output) < 0) + handle_error("pthread_create reader_thread failed\n"); + + return 0; +} + diff --git a/drivers/staging/ktap/userspace/lex.c b/drivers/staging/ktap/userspace/lex.c new file mode 100644 index 000000000000..484dd7fdafc3 --- /dev/null +++ b/drivers/staging/ktap/userspace/lex.c @@ -0,0 +1,622 @@ +/* + * lex.c - ktap lexical analyzer + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * Copyright (C) 1994-2013 Lua.org, PUC-Rio. + * - The part of code in this file is copied from lua initially. + * - lua's MIT license is compatible with GPL. + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include "../include/ktap_types.h" +#include "ktapc.h" + +#define next(ls) (ls->current = *ls->ptr++) + +#define currIsNewline(ls) (ls->current == '\n' || ls->current == '\r') + +#define KTAP_MINBUFFER 32 + +/* ORDER RESERVED */ +static const char *const ktap_tokens [] = { + "trace", "trace_end", "argevent", "argname", + "arg1", "arg2", "arg3", "arg4", "arg5", "arg6", "arg7", "arg9", "arg9", + "profile", "tick", + "and", "break", "do", "else", "elseif", + "end", "false", "for", "function", "goto", "if", + "in", "local", "nil", "not", "or", "repeat", + "return", "then", "true", "until", "while", + "..", "...", "==", ">=", "<=", "!=", "+=", "::", "", + "", "", "" +}; + +#define save_and_next(ls) (save(ls, ls->current), next(ls)) + +static void lexerror(ktap_lexstate *ls, const char *msg, int token); + +static void save(ktap_lexstate *ls, int c) +{ + ktap_mbuffer *b = ls->buff; + if (mbuff_len(b) + 1 > mbuff_size(b)) { + size_t newsize; + if (mbuff_size(b) >= MAX_SIZET / 2) + lexerror(ls, "lexical element too long", 0); + newsize = mbuff_size(b) * 2; + mbuff_resize(b, newsize); + } + b->buffer[mbuff_len(b)++] = (char)c; +} + +void lex_init() +{ + int i; + for (i = 0; i < NUM_RESERVED; i++) { + ktap_string *ts = ktapc_ts_new(ktap_tokens[i]); + ts->tsv.extra = (u8)(i+1); /* reserved word */ + } +} + +const char *lex_token2str(ktap_lexstate *ls, int token) +{ + if (token < FIRST_RESERVED) { + ktap_assert(token == (unsigned char)token); + return (isprint(token)) ? ktapc_sprintf(KTAP_QL("%c"), token) : + ktapc_sprintf("char(%d)", token); + } else { + const char *s = ktap_tokens[token - FIRST_RESERVED]; + if (token < TK_EOS) + return ktapc_sprintf(KTAP_QS, s); + else + return s; + } +} + +static const char *txtToken(ktap_lexstate *ls, int token) +{ + switch (token) { + case TK_NAME: + case TK_STRING: + case TK_NUMBER: + save(ls, '\0'); + return ktapc_sprintf(KTAP_QS, mbuff(ls->buff)); + default: + return lex_token2str(ls, token); + } +} + +static void lexerror(ktap_lexstate *ls, const char *msg, int token) +{ + char buff[KTAP_IDSIZE]; + char *newmsg; + + ktapc_chunkid(buff, getstr(ls->source), KTAP_IDSIZE); + newmsg = ktapc_sprintf("%s:%d: %s", buff, ls->linenumber, msg); + if (token) + newmsg = ktapc_sprintf("%s near %s", newmsg, txtToken(ls, token)); + printf("lexerror: %s\n", newmsg); + exit(EXIT_FAILURE); +} + +void lex_syntaxerror(ktap_lexstate *ls, const char *msg) +{ + lexerror(ls, msg, ls->t.token); +} + +/* + * creates a new string and anchors it in function's table so that + * it will not be collected until the end of the function's compilation + * (by that time it should be anchored in function's prototype) + */ +ktap_string *lex_newstring(ktap_lexstate *ls, const char *str, size_t l) +{ + const ktap_value *o; /* entry for `str' */ + ktap_value val; /* entry for `str' */ + ktap_value tsv; + ktap_string *ts = ktapc_ts_newlstr(str, l); /* create new string */ + setsvalue(&tsv, ts); + o = ktapc_table_get(ls->fs->h, &tsv); + if (ttisnil(o)) { /* not in use yet? (see 'addK') */ + /* boolean value does not need GC barrier; + table has no metatable, so it does not need to invalidate cache */ + setbvalue(&val, 1); /* t[string] = true */ + ktapc_table_setvalue(ls->fs->h, &tsv, &val); + } + return ts; +} + +/* + * increment line number and skips newline sequence (any of + * \n, \r, \n\r, or \r\n) + */ +static void inclinenumber(ktap_lexstate *ls) +{ + int old = ls->current; + ktap_assert(currIsNewline(ls)); + next(ls); /* skip `\n' or `\r' */ + if (currIsNewline(ls) && ls->current != old) + next(ls); /* skip `\n\r' or `\r\n' */ + if (++ls->linenumber >= MAX_INT) + lex_syntaxerror(ls, "chunk has too many lines"); +} + +void lex_setinput(ktap_lexstate *ls, char *ptr, ktap_string *source, int firstchar) +{ + ls->decpoint = '.'; + ls->current = firstchar; + ls->lookahead.token = TK_EOS; /* no look-ahead token */ + ls->ptr = ptr; + ls->fs = NULL; + ls->linenumber = 1; + ls->lastline = 1; + ls->source = source; + ls->envn = ktapc_ts_new(KTAP_ENV); /* create env name */ + mbuff_resize(ls->buff, KTAP_MINBUFFER); /* initialize buffer */ +} + +/* + * ======================================================= + * LEXICAL ANALYZER + * ======================================================= + */ +static int check_next(ktap_lexstate *ls, const char *set) +{ + if (ls->current == '\0' || !strchr(set, ls->current)) + return 0; + save_and_next(ls); + return 1; +} + +/* + * change all characters 'from' in buffer to 'to' + */ +static void buffreplace(ktap_lexstate *ls, char from, char to) +{ + size_t n = mbuff_len(ls->buff); + char *p = mbuff(ls->buff); + while (n--) + if (p[n] == from) p[n] = to; +} + +#if !defined(getlocaledecpoint) +#define getlocaledecpoint() (localeconv()->decimal_point[0]) +#endif + +#define mbuff2d(b,e) ktapc_str2d(mbuff(b), mbuff_len(b) - 1, e) + +/* + * in case of format error, try to change decimal point separator to + * the one defined in the current locale and check again + */ +static void trydecpoint(ktap_lexstate *ls, ktap_seminfo *seminfo) +{ + char old = ls->decpoint; + ls->decpoint = getlocaledecpoint(); + buffreplace(ls, old, ls->decpoint); /* try new decimal separator */ + if (!mbuff2d(ls->buff, &seminfo->r)) { + /* format error with correct decimal point: no more options */ + buffreplace(ls, ls->decpoint, '.'); /* undo change (for error message) */ + lexerror(ls, "malformed number", TK_NUMBER); + } +} + +/* + * this function is quite liberal in what it accepts, as 'ktapc_str2d' + * will reject ill-formed numerals. + */ +static void read_numeral(ktap_lexstate *ls, ktap_seminfo *seminfo) +{ + const char *expo = "Ee"; + int first = ls->current; + + ktap_assert(isdigit(ls->current)); + save_and_next(ls); + if (first == '0' && check_next(ls, "Xx")) /* hexadecimal? */ + expo = "Pp"; + for (;;) { + if (check_next(ls, expo)) /* exponent part? */ + check_next(ls, "+-"); /* optional exponent sign */ + if (isxdigit(ls->current) || ls->current == '.') + save_and_next(ls); + else + break; + } + save(ls, '\0'); + buffreplace(ls, '.', ls->decpoint); /* follow locale for decimal point */ + if (!mbuff2d(ls->buff, &seminfo->r)) /* format error? */ + trydecpoint(ls, seminfo); /* try to update decimal point separator */ +} + +/* + * skip a sequence '[=*[' or ']=*]' and return its number of '='s or + * -1 if sequence is malformed + */ +static int skip_sep(ktap_lexstate *ls) +{ + int count = 0; + int s = ls->current; + + ktap_assert(s == '[' || s == ']'); + save_and_next(ls); + while (ls->current == '=') { + save_and_next(ls); + count++; + } + return (ls->current == s) ? count : (-count) - 1; +} + +static void read_long_string(ktap_lexstate *ls, ktap_seminfo *seminfo, int sep) +{ + save_and_next(ls); /* skip 2nd `[' */ + if (currIsNewline(ls)) /* string starts with a newline? */ + inclinenumber(ls); /* skip it */ + for (;;) { + switch (ls->current) { + case EOZ: + lexerror(ls, (seminfo) ? "unfinished long string" : + "unfinished long comment", TK_EOS); + break; /* to avoid warnings */ + case ']': { + if (skip_sep(ls) == sep) { + save_and_next(ls); /* skip 2nd `]' */ + goto endloop; + } + break; + } + case '\n': + case '\r': { + save(ls, '\n'); + inclinenumber(ls); + /* avoid wasting space */ + if (!seminfo) + mbuff_reset(ls->buff); + break; + } + default: { + if (seminfo) + save_and_next(ls); + else + next(ls); + } + } + } + + endloop: + if (seminfo) + seminfo->ts = lex_newstring(ls, mbuff(ls->buff) + (2 + sep), + mbuff_len(ls->buff) - 2*(2 + sep)); +} + +static void escerror(ktap_lexstate *ls, int *c, int n, const char *msg) +{ + int i; + mbuff_reset(ls->buff); /* prepare error message */ + save(ls, '\\'); + for (i = 0; i < n && c[i] != EOZ; i++) + save(ls, c[i]); + lexerror(ls, msg, TK_STRING); +} + +static int readhexaesc(ktap_lexstate *ls) +{ + int c[3], i; /* keep input for error message */ + int r = 0; /* result accumulator */ + c[0] = 'x'; /* for error message */ + for (i = 1; i < 3; i++) { /* read two hexa digits */ + c[i] = next(ls); + if (!isxdigit(c[i])) + escerror(ls, c, i + 1, "hexadecimal digit expected"); + r = (r << 4) + ktapc_hexavalue(c[i]); + } + return r; +} + +static int readdecesc(ktap_lexstate *ls) +{ + int c[3], i; + int r = 0; /* result accumulator */ + for (i = 0; i < 3 && isdigit(ls->current); i++) { /* read up to 3 digits */ + c[i] = ls->current; + r = 10*r + c[i] - '0'; + next(ls); + } + if (r > UCHAR_MAX) + escerror(ls, c, i, "decimal escape too large"); + return r; +} + +static void read_string(ktap_lexstate *ls, int del, ktap_seminfo *seminfo) +{ + save_and_next(ls); /* keep delimiter (for error messages) */ + while (ls->current != del) { + switch (ls->current) { + case EOZ: + lexerror(ls, "unfinished string", TK_EOS); + break; /* to avoid warnings */ + case '\n': + case '\r': + lexerror(ls, "unfinished string", TK_STRING); + break; /* to avoid warnings */ + case '\\': { /* escape sequences */ + int c; /* final character to be saved */ + next(ls); /* do not save the `\' */ + switch (ls->current) { + case 'a': c = '\a'; goto read_save; + case 'b': c = '\b'; goto read_save; + case 'f': c = '\f'; goto read_save; + case 'n': c = '\n'; goto read_save; + case 'r': c = '\r'; goto read_save; + case 't': c = '\t'; goto read_save; + case 'v': c = '\v'; goto read_save; + case 'x': c = readhexaesc(ls); goto read_save; + case '\n': case '\r': + inclinenumber(ls); c = '\n'; goto only_save; + case '\\': case '\"': case '\'': + c = ls->current; goto read_save; + case EOZ: goto no_save; /* will raise an error next loop */ + case 'z': { /* zap following span of spaces */ + next(ls); /* skip the 'z' */ + while (isspace(ls->current)) { + if (currIsNewline(ls)) + inclinenumber(ls); + else + next(ls); + } + goto no_save; + } + default: { + if (!isdigit(ls->current)) + escerror(ls, &ls->current, 1, "invalid escape sequence"); + /* digital escape \ddd */ + c = readdecesc(ls); + goto only_save; + } + } + read_save: + next(ls); /* read next character */ + only_save: + save(ls, c); /* save 'c' */ + no_save: + break; + } + default: + save_and_next(ls); + } + } + save_and_next(ls); /* skip delimiter */ + seminfo->ts = lex_newstring(ls, mbuff(ls->buff) + 1, mbuff_len(ls->buff) - 2); +} + +static int llex(ktap_lexstate *ls, ktap_seminfo *seminfo) +{ + mbuff_reset(ls->buff); + + for (;;) { + switch (ls->current) { + case '\n': case '\r': { /* line breaks */ + inclinenumber(ls); + break; + } + case ' ': case '\f': case '\t': case '\v': { /* spaces */ + next(ls); + break; + } + case '#': { + while (!currIsNewline(ls) && ls->current != EOZ) + next(ls); /* skip until end of line (or end of file) */ + break; + } + #if 0 + case '-': { /* '-' or '--' (comment) */ + next(ls); + if (ls->current != '-') + return '-'; + /* else is a comment */ + next(ls); + if (ls->current == '[') { /* long comment? */ + int sep = skip_sep(ls); + mbuff_reset(ls->buff); /* `skip_sep' may dirty the buffer */ + if (sep >= 0) { + read_long_string(ls, NULL, sep); /* skip long comment */ + mbuff_reset(ls->buff); /* previous call may dirty the buff. */ + break; + } + } + /* else short comment */ + while (!currIsNewline(ls) && ls->current != EOZ) + next(ls); /* skip until end of line (or end of file) */ + break; + } + #endif + case '[': { /* long string or simply '[' */ + int sep = skip_sep(ls); + if (sep >= 0) { + read_long_string(ls, seminfo, sep); + return TK_STRING; + } + else if (sep == -1) + return '['; + else + lexerror(ls, "invalid long string delimiter", TK_STRING); + } + case '+': { + next(ls); + if (ls->current != '=') + return '+'; + else { + next(ls); + return TK_INCR; + } + } + case '=': { + next(ls); + if (ls->current != '=') + return '='; + else { + next(ls); + return TK_EQ; + } + } + case '<': { + next(ls); + if (ls->current != '=') + return '<'; + else { + next(ls); + return TK_LE; + } + } + case '>': { + next(ls); + if (ls->current != '=') + return '>'; + else { + next(ls); + return TK_GE; + } + } + case '!': { + next(ls); + if (ls->current != '=') + return TK_NOT; + else { + next(ls); + return TK_NE; + } + } + case ':': { + next(ls); + if (ls->current != ':') + return ':'; + else { + next(ls); + return TK_DBCOLON; + } + } + case '"': case '\'': { /* short literal strings */ + read_string(ls, ls->current, seminfo); + return TK_STRING; + } + case '.': { /* '.', '..', '...', or number */ + save_and_next(ls); + if (check_next(ls, ".")) { + if (check_next(ls, ".")) + return TK_DOTS; /* '...' */ + else + return TK_CONCAT; /* '..' */ + } + else if (!isdigit(ls->current)) + return '.'; + /* else go through */ + } + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': { + read_numeral(ls, seminfo); + return TK_NUMBER; + } + case EOZ: { + return TK_EOS; + } + case '&': { + next(ls); + if (ls->current != '&') + return '&'; + else { + next(ls); + return TK_AND; + } + } + case '|': { + next(ls); + if (ls->current != '|') + return '|'; + else { + next(ls); + return TK_OR; + } + } + default: { + if (islalpha(ls->current)) { + /* identifier or reserved word? */ + ktap_string *ts; + do { + save_and_next(ls); + } while (islalnum(ls->current)); + ts = lex_newstring(ls, mbuff(ls->buff), + mbuff_len(ls->buff)); + seminfo->ts = ts; + if (isreserved(ts)) /* reserved word? */ + return ts->tsv.extra - 1 + + FIRST_RESERVED; + else { + return TK_NAME; + } + } else { /* single-char tokens (+ - / ...) */ + int c = ls->current; + next(ls); + return c; + } + } + } + } +} + +void lex_read_string_until(ktap_lexstate *ls, int c) +{ + ktap_string *ts; + char errmsg[32]; + + mbuff_reset(ls->buff); + + while (ls->current == ' ') + next(ls); + + do { + save_and_next(ls); + } while (ls->current != c && ls->current != EOZ); + + if (ls->current != c) { + sprintf(errmsg, "expect %c", c); + lexerror(ls, errmsg, 0); + } + + ts = lex_newstring(ls, mbuff(ls->buff), mbuff_len(ls->buff)); + ls->t.seminfo.ts = ts; + ls->t.token = TK_STRING; +} + +void lex_next(ktap_lexstate *ls) +{ + ls->lastline = ls->linenumber; + if (ls->lookahead.token != TK_EOS) { /* is there a look-ahead token? */ + ls->t = ls->lookahead; /* use this one */ + ls->lookahead.token = TK_EOS; /* and discharge it */ + } else + ls->t.token = llex(ls, &ls->t.seminfo); /* read next token */ +} + +int lex_lookahead(ktap_lexstate *ls) +{ + ktap_assert(ls->lookahead.token == TK_EOS); + ls->lookahead.token = llex(ls, &ls->lookahead.seminfo); + return ls->lookahead.token; +} + diff --git a/drivers/staging/ktap/userspace/main.c b/drivers/staging/ktap/userspace/main.c new file mode 100644 index 000000000000..2aa686a99914 --- /dev/null +++ b/drivers/staging/ktap/userspace/main.c @@ -0,0 +1,609 @@ +/* + * main.c - ktap compiler and loader entry + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../include/ktap_types.h" +#include "../include/ktap_opcodes.h" +#include "ktapc.h" + + +/*******************************************************************/ + +void *ktapc_reallocv(void *block, size_t osize, size_t nsize) +{ + return kp_reallocv(NULL, block, osize, nsize); +} + +ktap_closure *ktapc_newlclosure(int n) +{ + return kp_newlclosure(NULL, n); +} + +ktap_proto *ktapc_newproto() +{ + return kp_newproto(NULL); +} + +const ktap_value *ktapc_table_get(ktap_table *t, const ktap_value *key) +{ + return kp_table_get(t, key); +} + +void ktapc_table_setvalue(ktap_table *t, const ktap_value *key, ktap_value *val) +{ + kp_table_setvalue(NULL, t, key, val); +} + +ktap_table *ktapc_table_new() +{ + return kp_table_new(NULL); +} + +ktap_string *ktapc_ts_newlstr(const char *str, size_t l) +{ + return kp_tstring_newlstr(NULL, str, l); +} + +ktap_string *ktapc_ts_new(const char *str) +{ + return kp_tstring_new(NULL, str); +} + +int ktapc_ts_eqstr(ktap_string *a, ktap_string *b) +{ + return kp_tstring_eqstr(a, b); +} + +static void ktapc_runerror(const char *err_msg_fmt, ...) +{ + va_list ap; + + fprintf(stderr, "ktapc_runerror\n"); + + va_start(ap, err_msg_fmt); + vfprintf(stderr, err_msg_fmt, ap); + va_end(ap); + + exit(EXIT_FAILURE); +} + +/* + * todo: memory leak here + */ +char *ktapc_sprintf(const char *fmt, ...) +{ + char *msg = malloc(128); + + va_list argp; + va_start(argp, fmt); + vsprintf(msg, fmt, argp); + va_end(argp); + return msg; +} + + +#define MINSIZEARRAY 4 + +void *ktapc_growaux(void *block, int *size, size_t size_elems, int limit, + const char *what) +{ + void *newblock; + int newsize; + + if (*size >= limit/2) { /* cannot double it? */ + if (*size >= limit) /* cannot grow even a little? */ + ktapc_runerror("too many %s (limit is %d)\n", + what, limit); + newsize = limit; /* still have at least one free place */ + } else { + newsize = (*size) * 2; + if (newsize < MINSIZEARRAY) + newsize = MINSIZEARRAY; /* minimum size */ + } + + newblock = ktapc_reallocv(block, (*size) * size_elems, newsize * size_elems); + *size = newsize; /* update only when everything else is OK */ + return newblock; +} + +/*************************************************************************/ + +#define print_base(i) \ + do { \ + if (i < f->sizelocvars) /* it's a localvars */ \ + printf("%s", getstr(f->locvars[i].varname)); \ + else \ + printf("base + %d", i); \ + } while (0) + +#define print_RKC(instr) \ + do { \ + if (ISK(GETARG_C(instr))) \ + kp_showobj(NULL, k + INDEXK(GETARG_C(instr))); \ + else \ + print_base(GETARG_C(instr)); \ + } while (0) + +static void decode_instruction(ktap_proto *f, int instr) +{ + int opcode = GET_OPCODE(instr); + ktap_value *k; + + k = f->k; + + printf("%.8x\t", instr); + printf("%s\t", ktap_opnames[opcode]); + + switch (opcode) { + case OP_GETTABUP: + print_base(GETARG_A(instr)); + printf(" <- "); + + if (GETARG_B(instr) == 0) + printf("global"); + else + printf("upvalues[%d]", GETARG_B(instr)); + + printf("{"); print_RKC(instr); printf("}"); + + break; + case OP_GETTABLE: + print_base(GETARG_A(instr)); + printf(" <- "); + + print_base(GETARG_B(instr)); + + printf("{"); + print_RKC(instr); + printf("}"); + break; + case OP_LOADK: + printf("\t"); + print_base(GETARG_A(instr)); + printf(" <- "); + + kp_showobj(NULL, k + GETARG_Bx(instr)); + break; + case OP_CALL: + printf("\t"); + print_base(GETARG_A(instr)); + break; + case OP_JMP: + printf("\t%d", GETARG_sBx(instr)); + break; + default: + break; + } + + printf("\n"); +} + +static int function_nr = 0; + +/* this is a debug function used for check bytecode chunk file */ +static void dump_function(int level, ktap_proto *f) +{ + int i; + + printf("\n----------------------------------------------------\n"); + printf("function %d [level %d]:\n", function_nr++, level); + printf("linedefined: %d\n", f->linedefined); + printf("lastlinedefined: %d\n", f->lastlinedefined); + printf("numparams: %d\n", f->numparams); + printf("is_vararg: %d\n", f->is_vararg); + printf("maxstacksize: %d\n", f->maxstacksize); + printf("source: %s\n", getstr(f->source)); + printf("sizelineinfo: %d \t", f->sizelineinfo); + for (i = 0; i < f->sizelineinfo; i++) + printf("%d ", f->lineinfo[i]); + printf("\n"); + + printf("sizek: %d\n", f->sizek); + for (i = 0; i < f->sizek; i++) { + switch(f->k[i].type) { + case KTAP_TNIL: + printf("\tNIL\n"); + break; + case KTAP_TBOOLEAN: + printf("\tBOOLEAN: "); + printf("%d\n", f->k[i].val.b); + break; + case KTAP_TNUMBER: + printf("\tTNUMBER: "); + printf("%ld\n", f->k[i].val.n); + break; + case KTAP_TSTRING: + printf("\tTSTRING: "); + printf("%s\n", svalue(&(f->k[i]))); + + break; + default: + printf("\terror: unknow constant type\n"); + } + } + + printf("sizelocvars: %d\n", f->sizelocvars); + for (i = 0; i < f->sizelocvars; i++) { + printf("\tlocvars: %s startpc: %d endpc: %d\n", + getstr(f->locvars[i].varname), f->locvars[i].startpc, + f->locvars[i].endpc); + } + + printf("sizeupvalues: %d\n", f->sizeupvalues); + for (i = 0; i < f->sizeupvalues; i++) { + printf("\tname: %s instack: %d idx: %d\n", + getstr(f->upvalues[i].name), f->upvalues[i].instack, + f->upvalues[i].idx); + } + + printf("\n"); + printf("sizecode: %d\n", f->sizecode); + for (i = 0; i < f->sizecode; i++) + decode_instruction(f, f->code[i]); + + printf("sizep: %d\n", f->sizep); + for (i = 0; i < f->sizep; i++) + dump_function(level + 1, f->p[i]); + +} + +static void usage(const char *msg_fmt, ...) +{ + va_list ap; + + va_start(ap, msg_fmt); + fprintf(stderr, msg_fmt, ap); + va_end(ap); + + fprintf(stderr, +"Usage: ktap [options] file [script args] -- cmd [args]\n" +" or: ktap [options] -e one-liner -- cmd [args]\n" +"\n" +"Options and arguments:\n" +" -o file : send script output to file, instead of stderr\n" +" -p pid : specific tracing pid\n" +" -C cpu : cpu to monitor in system-wide\n" +" -T : show timestamp for event\n" +" -V : show version\n" +" -v : enable verbose mode\n" +" -s : simple event tracing\n" +" -b : list byte codes\n" +" file : program read from script file\n" +" -- cmd [args] : workload to tracing\n"); + + exit(EXIT_FAILURE); +} + +ktap_global_state dummy_global_state; + +static void init_dummy_global_state() +{ + memset(&dummy_global_state, 0, sizeof(ktap_global_state)); + dummy_global_state.seed = 201236; + + kp_tstring_resize(NULL, 32); /* set inital string hashtable size */ +} + +#define handle_error(str) do { perror(str); exit(-1); } while(0) + +static struct ktap_parm uparm; +static int ktap_trunk_mem_size = 1024; + +static int ktapc_writer(const void* p, size_t sz, void* ud) +{ + if (uparm.trunk_len + sz > ktap_trunk_mem_size) { + int new_size = (uparm.trunk_len + sz) * 2; + uparm.trunk = realloc(uparm.trunk, new_size); + ktap_trunk_mem_size = new_size; + } + + memcpy(uparm.trunk + uparm.trunk_len, p, sz); + uparm.trunk_len += sz; + + return 0; +} + + +static int forks; +static char **workload_argv; + +static int fork_workload(int ktap_fd) +{ + int pid; + + pid = fork(); + if (pid < 0) + handle_error("failed to fork"); + + if (pid > 0) + return pid; + + signal(SIGTERM, SIG_DFL); + + execvp("", workload_argv); + + /* + * waiting ktapvm prepare all tracing event + * make it more robust in future. + */ + pause(); + + execvp(workload_argv[0], workload_argv); + + perror(workload_argv[0]); + exit(-1); + + return -1; +} + +#define KTAPVM_PATH "/sys/kernel/debug/ktap/ktapvm" + +static char *output_filename; +pid_t ktap_pid; + +static int run_ktapvm() +{ + int ktapvm_fd, ktap_fd; + int ret; + + ktap_pid = getpid(); + + ktapvm_fd = open(KTAPVM_PATH, O_RDONLY); + if (ktapvm_fd < 0) + handle_error("open " KTAPVM_PATH " failed"); + + ktap_fd = ioctl(ktapvm_fd, 0, NULL); + if (ktap_fd < 0) + handle_error("ioctl ktapvm failed"); + + ktapio_create(output_filename); + + if (forks) { + uparm.trace_pid = fork_workload(ktap_fd); + uparm.workload = 1; + } + + ret = ioctl(ktap_fd, KTAP_CMD_IOC_RUN, &uparm); + + close(ktap_fd); + close(ktapvm_fd); + + return ret; +} + +int verbose; +static int dump_bytecode; +static char oneline_src[1024]; +static int trace_pid = -1; +static int trace_cpu = -1; +static int print_timestamp; + +#define SIMPLE_ONE_LINER_FMT \ + "trace %s { print(cpu(), tid(), execname(), argevent) }" + +static const char *script_file; +static int script_args_start; +static int script_args_end; + +static void parse_option(int argc, char **argv) +{ + char pid[32] = {0}; + char cpu_str[32] = {0}; + char *next_arg; + int i, j; + + for (i = 1; i < argc; i++) { + if (argv[i][0] != '-') { + script_file = argv[i]; + if (!script_file) + usage(""); + + script_args_start = i + 1; + script_args_end = argc; + + for (j = i + 1; j < argc; j++) { + if (argv[j][0] == '-' && argv[j][1] == '-') + goto found_cmd; + } + + return; + } + + if (argv[i][0] == '-' && argv[i][1] == '-') { + j = i; + goto found_cmd; + } + + next_arg = argv[i + 1]; + + switch (argv[i][1]) { + case 'o': + output_filename = malloc(strlen(next_arg) + 1); + if (!output_filename) + return; + + strncpy(output_filename, next_arg, strlen(next_arg)); + i++; + break; + case 'e': + strncpy(oneline_src, next_arg, strlen(next_arg)); + i++; + break; + case 'p': + strncpy(pid, next_arg, strlen(next_arg)); + trace_pid = atoi(pid); + i++; + break; + case 'C': + strncpy(cpu_str, next_arg, strlen(next_arg)); + trace_cpu = atoi(cpu_str); + i++; + break; + case 'T': + print_timestamp = 1; + break; + case 'v': + verbose = 1; + break; + case 's': + sprintf(oneline_src, SIMPLE_ONE_LINER_FMT, next_arg); + i++; + break; + case 'b': + dump_bytecode = 1; + break; + case 'V': + case '?': + case 'h': + usage(""); + break; + default: + usage("wrong argument\n"); + break; + } + } + + return; + + found_cmd: + script_args_end = j; + forks = 1; + workload_argv = &argv[j + 1]; +} + +static void compile(const char *input) +{ + ktap_closure *cl; + char *buff; + struct stat sb; + int fdin; + + if (oneline_src[0] != '\0') { + init_dummy_global_state(); + cl = ktapc_parser(oneline_src, input); + goto dump; + } + + fdin = open(input, O_RDONLY); + if (fdin < 0) { + fprintf(stderr, "open file %s failed\n", input); + exit(-1); + } + + if (fstat(fdin, &sb) == -1) + handle_error("fstat failed"); + + buff = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fdin, 0); + if (buff == MAP_FAILED) + handle_error("mmap failed"); + + init_dummy_global_state(); + cl = ktapc_parser(buff, input); + + munmap(buff, sb.st_size); + close(fdin); + + dump: + if (dump_bytecode) { + dump_function(1, cl->l.p); + exit(0); + } + + /* ktapc output */ + uparm.trunk = malloc(ktap_trunk_mem_size); + if (!uparm.trunk) + handle_error("malloc failed"); + + ktapc_dump(cl->l.p, ktapc_writer, NULL, 0); +} + +int main(int argc, char **argv) +{ + char **ktapvm_argv; + int new_index, i; + int ret; + + if (argc == 1) + usage(""); + + parse_option(argc, argv); + + if (oneline_src[0] != '\0') + script_file = "one-liner"; + + compile(script_file); + + ktapvm_argv = (char **)malloc(sizeof(char *)*(script_args_end - + script_args_start + 1)); + if (!ktapvm_argv) { + fprintf(stderr, "canno allocate ktapvm_argv\n"); + return -1; + } + + ktapvm_argv[0] = malloc(strlen(script_file) + 1); + if (!ktapvm_argv[0]) { + fprintf(stderr, "canno allocate memory\n"); + return -1; + } + strcpy(ktapvm_argv[0], script_file); + ktapvm_argv[0][strlen(script_file)] = '\0'; + + /* pass rest argv into ktapvm */ + new_index = 1; + for (i = script_args_start; i < script_args_end; i++) { + ktapvm_argv[new_index] = malloc(strlen(argv[i]) + 1); + if (!ktapvm_argv[new_index]) { + fprintf(stderr, "canno allocate memory\n"); + return -1; + } + strcpy(ktapvm_argv[new_index], argv[i]); + ktapvm_argv[new_index][strlen(argv[i])] = '\0'; + new_index++; + } + + uparm.argv = ktapvm_argv; + uparm.argc = new_index; + uparm.verbose = verbose; + uparm.trace_pid = trace_pid; + uparm.trace_cpu = trace_cpu; + uparm.print_timestamp = print_timestamp; + + /* start running into kernel ktapvm */ + ret = run_ktapvm(); + + cleanup_event_resources(); + return ret; +} + + diff --git a/drivers/staging/ktap/userspace/parser.c b/drivers/staging/ktap/userspace/parser.c new file mode 100644 index 000000000000..dd9e2de692ad --- /dev/null +++ b/drivers/staging/ktap/userspace/parser.c @@ -0,0 +1,1910 @@ +/* + * parser.c - ktap parser + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * Copyright (C) 1994-2013 Lua.org, PUC-Rio. + * - The part of code in this file is copied from lua initially. + * - lua's MIT license is compatible with GPL. + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +#include "../include/ktap_types.h" +#include "../include/ktap_opcodes.h" +#include "ktapc.h" + +/* maximum number of local variables per function (must be smaller + than 250, due to the bytecode format) */ +#define MAXVARS 200 + +#define hasmultret(k) ((k) == VCALL || (k) == VVARARG) + + +/* + * nodes for block list (list of active blocks) + */ +typedef struct ktap_blockcnt { + struct ktap_blockcnt *previous; /* chain */ + short firstlabel; /* index of first label in this block */ + short firstgoto; /* index of first pending goto in this block */ + u8 nactvar; /* # active locals outside the block */ + u8 upval; /* true if some variable in the block is an upvalue */ + u8 isloop; /* true if `block' is a loop */ +} ktap_blockcnt; + +/* + * prototypes for recursive non-terminal functions + */ +static void statement (ktap_lexstate *ls); +static void expr (ktap_lexstate *ls, ktap_expdesc *v); + +static void anchor_token(ktap_lexstate *ls) +{ + /* last token from outer function must be EOS */ + ktap_assert((int)(ls->fs != NULL) || ls->t.token == TK_EOS); + if (ls->t.token == TK_NAME || ls->t.token == TK_STRING) { + ktap_string *ts = ls->t.seminfo.ts; + lex_newstring(ls, getstr(ts), ts->tsv.len); + } +} + +/* semantic error */ +static void semerror(ktap_lexstate *ls, const char *msg) +{ + ls->t.token = 0; /* remove 'near to' from final message */ + lex_syntaxerror(ls, msg); +} + +static void error_expected(ktap_lexstate *ls, int token) +{ + lex_syntaxerror(ls, + ktapc_sprintf("%s expected", lex_token2str(ls, token))); +} + +static void errorlimit(ktap_funcstate *fs, int limit, const char *what) +{ + const char *msg; + int line = fs->f->linedefined; + const char *where = (line == 0) ? "main function" + : ktapc_sprintf("function at line %d", line); + + msg = ktapc_sprintf("too many %s (limit is %d) in %s", + what, limit, where); + lex_syntaxerror(fs->ls, msg); +} + +static void checklimit(ktap_funcstate *fs, int v, int l, const char *what) +{ + if (v > l) + errorlimit(fs, l, what); +} + +static int testnext(ktap_lexstate *ls, int c) +{ + if (ls->t.token == c) { + lex_next(ls); + return 1; + } + else + return 0; +} + +static void check(ktap_lexstate *ls, int c) +{ + if (ls->t.token != c) + error_expected(ls, c); +} + +static void checknext(ktap_lexstate *ls, int c) +{ + check(ls, c); + lex_next(ls); +} + +#define check_condition(ls,c,msg) { if (!(c)) lex_syntaxerror(ls, msg); } + +static void check_match(ktap_lexstate *ls, int what, int who, int where) +{ + if (!testnext(ls, what)) { + if (where == ls->linenumber) + error_expected(ls, what); + else { + lex_syntaxerror(ls, ktapc_sprintf( + "%s expected (to close %s at line %d)", + lex_token2str(ls, what), + lex_token2str(ls, who), where)); + } + } +} + +static ktap_string *str_checkname(ktap_lexstate *ls) +{ + ktap_string *ts; + + check(ls, TK_NAME); + ts = ls->t.seminfo.ts; + lex_next(ls); + return ts; +} + +static void init_exp(ktap_expdesc *e, expkind k, int i) +{ + e->f = e->t = NO_JUMP; + e->k = k; + e->u.info = i; +} + +static void codestring(ktap_lexstate *ls, ktap_expdesc *e, ktap_string *s) +{ + init_exp(e, VK, codegen_stringK(ls->fs, s)); +} + +static void checkname(ktap_lexstate *ls, ktap_expdesc *e) +{ + codestring(ls, e, str_checkname(ls)); +} + +static int registerlocalvar(ktap_lexstate *ls, ktap_string *varname) +{ + ktap_funcstate *fs = ls->fs; + ktap_proto *f = fs->f; + int oldsize = f->sizelocvars; + + ktapc_growvector(f->locvars, fs->nlocvars, f->sizelocvars, + ktap_locvar, SHRT_MAX, "local variables"); + + while (oldsize < f->sizelocvars) + f->locvars[oldsize++].varname = NULL; + + f->locvars[fs->nlocvars].varname = varname; + return fs->nlocvars++; +} + +static void new_localvar(ktap_lexstate *ls, ktap_string *name) +{ + ktap_funcstate *fs = ls->fs; + ktap_dyndata *dyd = ls->dyd; + int reg = registerlocalvar(ls, name); + + checklimit(fs, dyd->actvar.n + 1 - fs->firstlocal, + MAXVARS, "local variables"); + ktapc_growvector(dyd->actvar.arr, dyd->actvar.n + 1, + dyd->actvar.size, ktap_vardesc, MAX_INT, "local variables"); + dyd->actvar.arr[dyd->actvar.n++].idx = (short)reg; +} + +static void new_localvarliteral_(ktap_lexstate *ls, const char *name, size_t sz) +{ + new_localvar(ls, lex_newstring(ls, name, sz)); +} + +#define new_localvarliteral(ls,v) \ + new_localvarliteral_(ls, "" v, (sizeof(v)/sizeof(char))-1) + +static ktap_locvar *getlocvar(ktap_funcstate *fs, int i) +{ + int idx = fs->ls->dyd->actvar.arr[fs->firstlocal + i].idx; + + ktap_assert(idx < fs->nlocvars); + return &fs->f->locvars[idx]; +} + +static void adjustlocalvars(ktap_lexstate *ls, int nvars) +{ + ktap_funcstate *fs = ls->fs; + + fs->nactvar = (u8)(fs->nactvar + nvars); + for (; nvars; nvars--) { + getlocvar(fs, fs->nactvar - nvars)->startpc = fs->pc; + } +} + +static void removevars(ktap_funcstate *fs, int tolevel) +{ + fs->ls->dyd->actvar.n -= (fs->nactvar - tolevel); + + while (fs->nactvar > tolevel) + getlocvar(fs, --fs->nactvar)->endpc = fs->pc; +} + +static int searchupvalue(ktap_funcstate *fs, ktap_string *name) +{ + int i; + ktap_upvaldesc *up = fs->f->upvalues; + + for (i = 0; i < fs->nups; i++) { + if (ktapc_ts_eqstr(up[i].name, name)) + return i; + } + return -1; /* not found */ +} + +static int newupvalue(ktap_funcstate *fs, ktap_string *name, ktap_expdesc *v) +{ + ktap_proto *f = fs->f; + int oldsize = f->sizeupvalues; + + checklimit(fs, fs->nups + 1, MAXUPVAL, "upvalues"); + ktapc_growvector(f->upvalues, fs->nups, f->sizeupvalues, + ktap_upvaldesc, MAXUPVAL, "upvalues"); + + while (oldsize < f->sizeupvalues) + f->upvalues[oldsize++].name = NULL; + f->upvalues[(int)fs->nups].instack = (v->k == VLOCAL); + f->upvalues[(int)fs->nups].idx = (u8)(v->u.info); + f->upvalues[(int)fs->nups].name = name; + return fs->nups++; +} + +static int searchvar(ktap_funcstate *fs, ktap_string *n) +{ + int i; + + for (i = fs->nactvar-1; i >= 0; i--) { + if (ktapc_ts_eqstr(n, getlocvar(fs, i)->varname)) + return i; + } + return -1; /* not found */ +} + +/* + * Mark block where variable at given level was defined + * (to emit close instructions later). + */ +static void markupval(ktap_funcstate *fs, int level) +{ + ktap_blockcnt *bl = fs->bl; + + while (bl->nactvar > level) + bl = bl->previous; + bl->upval = 1; +} + +/* + * Find variable with given name 'n'. If it is an upvalue, add this + * upvalue into all intermediate functions. + */ +static int singlevaraux(ktap_funcstate *fs, ktap_string *n, ktap_expdesc *var, int base) +{ + if (fs == NULL) /* no more levels? */ + return VVOID; /* default is global */ + else { + int v = searchvar(fs, n); /* look up locals at current level */ + if (v >= 0) { /* found? */ + init_exp(var, VLOCAL, v); /* variable is local */ + if (!base) + markupval(fs, v); /* local will be used as an upval */ + return VLOCAL; + } else { /* not found as local at current level; try upvalues */ + int idx = searchupvalue(fs, n); /* try existing upvalues */ + if (idx < 0) { /* not found? */ + if (singlevaraux(fs->prev, n, var, 0) == VVOID) /* try upper levels */ + return VVOID; /* not found; is a global */ + /* else was LOCAL or UPVAL */ + idx = newupvalue(fs, n, var); /* will be a new upvalue */ + } + init_exp(var, VUPVAL, idx); + return VUPVAL; + } + } +} + +static void singlevar(ktap_lexstate *ls, ktap_expdesc *var) +{ + ktap_string *varname = str_checkname(ls); + ktap_funcstate *fs = ls->fs; + + if (singlevaraux(fs, varname, var, 1) == VVOID) { /* global name? */ + ktap_expdesc key; + singlevaraux(fs, ls->envn, var, 1); /* get environment variable */ + ktap_assert(var->k == VLOCAL || var->k == VUPVAL); + codestring(ls, &key, varname); /* key is variable name */ + codegen_indexed(fs, var, &key); /* env[varname] */ + } +} + +static void adjust_assign(ktap_lexstate *ls, int nvars, int nexps, ktap_expdesc *e) +{ + ktap_funcstate *fs = ls->fs; + int extra = nvars - nexps; + + if (hasmultret(e->k)) { + extra++; /* includes call itself */ + if (extra < 0) + extra = 0; + codegen_setreturns(fs, e, extra); /* last exp. provides the difference */ + if (extra > 1) + codegen_reserveregs(fs, extra-1); + } else { + if (e->k != VVOID) + codegen_exp2nextreg(fs, e); /* close last expression */ + if (extra > 0) { + int reg = fs->freereg; + + codegen_reserveregs(fs, extra); + codegen_nil(fs, reg, extra); + } + } +} + +static void enterlevel(ktap_lexstate *ls) +{ + ++ls->nCcalls; + checklimit(ls->fs, ls->nCcalls, KTAP_MAXCCALLS, "C levels"); +} + +static void closegoto(ktap_lexstate *ls, int g, ktap_labeldesc *label) +{ + int i; + ktap_funcstate *fs = ls->fs; + ktap_labellist *gl = &ls->dyd->gt; + ktap_labeldesc *gt = &gl->arr[g]; + + ktap_assert(ktapc_ts_eqstr(gt->name, label->name)); + if (gt->nactvar < label->nactvar) { + ktap_string *vname = getlocvar(fs, gt->nactvar)->varname; + const char *msg = ktapc_sprintf( + " at line %d jumps into the scope of local " KTAP_QS, + getstr(gt->name), gt->line, getstr(vname)); + semerror(ls, msg); + } + + codegen_patchlist(fs, gt->pc, label->pc); + /* remove goto from pending list */ + for (i = g; i < gl->n - 1; i++) + gl->arr[i] = gl->arr[i + 1]; + gl->n--; +} + +/* + * try to close a goto with existing labels; this solves backward jumps + */ +static int findlabel(ktap_lexstate *ls, int g) +{ + int i; + ktap_blockcnt *bl = ls->fs->bl; + ktap_dyndata *dyd = ls->dyd; + ktap_labeldesc *gt = &dyd->gt.arr[g]; + + /* check labels in current block for a match */ + for (i = bl->firstlabel; i < dyd->label.n; i++) { + ktap_labeldesc *lb = &dyd->label.arr[i]; + if (ktapc_ts_eqstr(lb->name, gt->name)) { /* correct label? */ + if (gt->nactvar > lb->nactvar && + (bl->upval || dyd->label.n > bl->firstlabel)) + codegen_patchclose(ls->fs, gt->pc, lb->nactvar); + closegoto(ls, g, lb); /* close it */ + return 1; + } + } + return 0; /* label not found; cannot close goto */ +} + +static int newlabelentry(ktap_lexstate *ls, ktap_labellist *l, ktap_string *name, + int line, int pc) +{ + int n = l->n; + + ktapc_growvector(l->arr, n, l->size, + ktap_labeldesc, SHRT_MAX, "labels/gotos"); + l->arr[n].name = name; + l->arr[n].line = line; + l->arr[n].nactvar = ls->fs->nactvar; + l->arr[n].pc = pc; + l->n++; + return n; +} + + +/* + * check whether new label 'lb' matches any pending gotos in current + * block; solves forward jumps + */ +static void findgotos(ktap_lexstate *ls, ktap_labeldesc *lb) +{ + ktap_labellist *gl = &ls->dyd->gt; + int i = ls->fs->bl->firstgoto; + + while (i < gl->n) { + if (ktapc_ts_eqstr(gl->arr[i].name, lb->name)) + closegoto(ls, i, lb); + else + i++; + } +} + +/* + * "export" pending gotos to outer level, to check them against + * outer labels; if the block being exited has upvalues, and + * the goto exits the scope of any variable (which can be the + * upvalue), close those variables being exited. + */ +static void movegotosout(ktap_funcstate *fs, ktap_blockcnt *bl) +{ + int i = bl->firstgoto; + ktap_labellist *gl = &fs->ls->dyd->gt; + + /* correct pending gotos to current block and try to close it + with visible labels */ + while (i < gl->n) { + ktap_labeldesc *gt = &gl->arr[i]; + + if (gt->nactvar > bl->nactvar) { + if (bl->upval) + codegen_patchclose(fs, gt->pc, bl->nactvar); + gt->nactvar = bl->nactvar; + } + if (!findlabel(fs->ls, i)) + i++; /* move to next one */ + } +} + +static void enterblock(ktap_funcstate *fs, ktap_blockcnt *bl, u8 isloop) +{ + bl->isloop = isloop; + bl->nactvar = fs->nactvar; + bl->firstlabel = fs->ls->dyd->label.n; + bl->firstgoto = fs->ls->dyd->gt.n; + bl->upval = 0; + bl->previous = fs->bl; + fs->bl = bl; + ktap_assert(fs->freereg == fs->nactvar); +} + + +/* + * create a label named "break" to resolve break statements + */ +static void breaklabel(ktap_lexstate *ls) +{ + ktap_string *n = ktapc_ts_new("break"); + int l = newlabelentry(ls, &ls->dyd->label, n, 0, ls->fs->pc); + + findgotos(ls, &ls->dyd->label.arr[l]); +} + +/* + * generates an error for an undefined 'goto'; choose appropriate + * message when label name is a reserved word (which can only be 'break') + */ +static void undefgoto(ktap_lexstate *ls, ktap_labeldesc *gt) +{ + const char *msg = isreserved(gt->name) + ? "<%s> at line %d not inside a loop" + : "no visible label " KTAP_QS " for at line %d"; + + msg = ktapc_sprintf(msg, getstr(gt->name), gt->line); + semerror(ls, msg); +} + +static void leaveblock(ktap_funcstate *fs) +{ + ktap_blockcnt *bl = fs->bl; + ktap_lexstate *ls = fs->ls; + if (bl->previous && bl->upval) { + /* create a 'jump to here' to close upvalues */ + int j = codegen_jump(fs); + + codegen_patchclose(fs, j, bl->nactvar); + codegen_patchtohere(fs, j); + } + + if (bl->isloop) + breaklabel(ls); /* close pending breaks */ + + fs->bl = bl->previous; + removevars(fs, bl->nactvar); + ktap_assert(bl->nactvar == fs->nactvar); + fs->freereg = fs->nactvar; /* free registers */ + ls->dyd->label.n = bl->firstlabel; /* remove local labels */ + if (bl->previous) /* inner block? */ + movegotosout(fs, bl); /* update pending gotos to outer block */ + else if (bl->firstgoto < ls->dyd->gt.n) /* pending gotos in outer block? */ + undefgoto(ls, &ls->dyd->gt.arr[bl->firstgoto]); /* error */ +} + +/* + * adds a new prototype into list of prototypes + */ +static ktap_proto *addprototype(ktap_lexstate *ls) +{ + ktap_proto *clp; + ktap_funcstate *fs = ls->fs; + ktap_proto *f = fs->f; /* prototype of current function */ + + if (fs->np >= f->sizep) { + int oldsize = f->sizep; + ktapc_growvector(f->p, fs->np, f->sizep, ktap_proto *, MAXARG_Bx, "functions"); + while (oldsize < f->sizep) + f->p[oldsize++] = NULL; + } + f->p[fs->np++] = clp = ktapc_newproto(); + return clp; +} + +/* + * codes instruction to create new closure in parent function + */ +static void codeclosure(ktap_lexstate *ls, ktap_expdesc *v) +{ + ktap_funcstate *fs = ls->fs->prev; + init_exp(v, VRELOCABLE, codegen_codeABx(fs, OP_CLOSURE, 0, fs->np - 1)); + codegen_exp2nextreg(fs, v); /* fix it at stack top (for GC) */ +} + +static void open_func(ktap_lexstate *ls, ktap_funcstate *fs, ktap_blockcnt *bl) +{ + ktap_proto *f; + + fs->prev = ls->fs; /* linked list of funcstates */ + fs->ls = ls; + ls->fs = fs; + fs->pc = 0; + fs->lasttarget = 0; + fs->jpc = NO_JUMP; + fs->freereg = 0; + fs->nk = 0; + fs->np = 0; + fs->nups = 0; + fs->nlocvars = 0; + fs->nactvar = 0; + fs->firstlocal = ls->dyd->actvar.n; + fs->bl = NULL; + f = fs->f; + f->source = ls->source; + f->maxstacksize = 2; /* registers 0/1 are always valid */ + fs->h = ktapc_table_new(); + //table_resize(NULL, fs->h, 32, 32); + enterblock(fs, bl, 0); +} + +static void close_func(ktap_lexstate *ls) +{ + ktap_funcstate *fs = ls->fs; + ktap_proto *f = fs->f; + + codegen_ret(fs, 0, 0); /* final return */ + leaveblock(fs); + ktapc_reallocvector(f->code, f->sizecode, fs->pc, ktap_instruction); + f->sizecode = fs->pc; + ktapc_reallocvector(f->lineinfo, f->sizelineinfo, fs->pc, int); + f->sizelineinfo = fs->pc; + ktapc_reallocvector(f->k, f->sizek, fs->nk, ktap_value); + f->sizek = fs->nk; + ktapc_reallocvector(f->p, f->sizep, fs->np, ktap_proto *); + f->sizep = fs->np; + ktapc_reallocvector(f->locvars, f->sizelocvars, fs->nlocvars, ktap_locvar); + f->sizelocvars = fs->nlocvars; + ktapc_reallocvector(f->upvalues, f->sizeupvalues, fs->nups, ktap_upvaldesc); + f->sizeupvalues = fs->nups; + ktap_assert((int)(fs->bl == NULL)); + ls->fs = fs->prev; + /* last token read was anchored in defunct function; must re-anchor it */ + anchor_token(ls); +} + +/*============================================================*/ +/* GRAMMAR RULES */ +/*============================================================*/ + +/* + * check whether current token is in the follow set of a block. + * 'until' closes syntactical blocks, but do not close scope, + * so it handled in separate. + */ +static int block_follow(ktap_lexstate *ls, int withuntil) +{ + switch (ls->t.token) { + case TK_ELSE: case TK_ELSEIF: + case TK_END: case TK_EOS: + return 1; + case TK_UNTIL: + return withuntil; + case '}': + return 1; + default: + return 0; + } +} + +static void statlist(ktap_lexstate *ls) +{ + /* statlist -> { stat [`;'] } */ + while (!block_follow(ls, 1)) { + if (ls->t.token == TK_RETURN) { + statement(ls); + return; /* 'return' must be last statement */ + } + statement(ls); + } +} + +static void fieldsel(ktap_lexstate *ls, ktap_expdesc *v) +{ + /* fieldsel -> ['.' | ':'] NAME */ + ktap_funcstate *fs = ls->fs; + ktap_expdesc key; + + codegen_exp2anyregup(fs, v); + lex_next(ls); /* skip the dot or colon */ + checkname(ls, &key); + codegen_indexed(fs, v, &key); +} + +static void yindex(ktap_lexstate *ls, ktap_expdesc *v) +{ + /* index -> '[' expr ']' */ + lex_next(ls); /* skip the '[' */ + expr(ls, v); + codegen_exp2val(ls->fs, v); + checknext(ls, ']'); +} + +/* + * {====================================================================== + * Rules for Constructors + * ======================================================================= + */ +struct ConsControl { + ktap_expdesc v; /* last list item read */ + ktap_expdesc *t; /* table descriptor */ + int nh; /* total number of `record' elements */ + int na; /* total number of array elements */ + int tostore; /* number of array elements pending to be stored */ +}; + +static void recfield(ktap_lexstate *ls, struct ConsControl *cc) +{ + /* recfield -> (NAME | `['exp1`]') = exp1 */ + ktap_funcstate *fs = ls->fs; + int reg = ls->fs->freereg; + ktap_expdesc key, val; + int rkkey; + + if (ls->t.token == TK_NAME) { + checklimit(fs, cc->nh, MAX_INT, "items in a constructor"); + checkname(ls, &key); + } else /* ls->t.token == '[' */ + yindex(ls, &key); + + cc->nh++; + checknext(ls, '='); + rkkey = codegen_exp2RK(fs, &key); + expr(ls, &val); + codegen_codeABC(fs, OP_SETTABLE, cc->t->u.info, rkkey, codegen_exp2RK(fs, &val)); + fs->freereg = reg; /* free registers */ +} + +static void closelistfield(ktap_funcstate *fs, struct ConsControl *cc) +{ + if (cc->v.k == VVOID) + return; /* there is no list item */ + codegen_exp2nextreg(fs, &cc->v); + cc->v.k = VVOID; + if (cc->tostore == LFIELDS_PER_FLUSH) { + codegen_setlist(fs, cc->t->u.info, cc->na, cc->tostore); /* flush */ + cc->tostore = 0; /* no more items pending */ + } +} + +static void lastlistfield(ktap_funcstate *fs, struct ConsControl *cc) +{ + if (cc->tostore == 0) + return; + + if (hasmultret(cc->v.k)) { + codegen_setmultret(fs, &cc->v); + codegen_setlist(fs, cc->t->u.info, cc->na, KTAP_MULTRET); + cc->na--; /* do not count last expression (unknown number of elements) */ + } else { + if (cc->v.k != VVOID) + codegen_exp2nextreg(fs, &cc->v); + codegen_setlist(fs, cc->t->u.info, cc->na, cc->tostore); + } +} + +static void listfield(ktap_lexstate *ls, struct ConsControl *cc) +{ + /* listfield -> exp */ + expr(ls, &cc->v); + checklimit(ls->fs, cc->na, MAX_INT, "items in a constructor"); + cc->na++; + cc->tostore++; +} + +static void field(ktap_lexstate *ls, struct ConsControl *cc) +{ + /* field -> listfield | recfield */ + switch(ls->t.token) { + case TK_NAME: { /* may be 'listfield' or 'recfield' */ + if (lex_lookahead(ls) != '=') /* expression? */ + listfield(ls, cc); + else + recfield(ls, cc); + break; + } + case '[': { + recfield(ls, cc); + break; + } + default: + listfield(ls, cc); + break; + } +} + +static void constructor(ktap_lexstate *ls, ktap_expdesc *t) +{ + /* constructor -> '{' [ field { sep field } [sep] ] '}' + sep -> ',' | ';' */ + ktap_funcstate *fs = ls->fs; + int line = ls->linenumber; + int pc = codegen_codeABC(fs, OP_NEWTABLE, 0, 0, 0); + struct ConsControl cc; + + cc.na = cc.nh = cc.tostore = 0; + cc.t = t; + init_exp(t, VRELOCABLE, pc); + init_exp(&cc.v, VVOID, 0); /* no value (yet) */ + codegen_exp2nextreg(ls->fs, t); /* fix it at stack top */ + checknext(ls, '{'); + do { + ktap_assert(cc.v.k == VVOID || cc.tostore > 0); + if (ls->t.token == '}') + break; + closelistfield(fs, &cc); + field(ls, &cc); + } while (testnext(ls, ',') || testnext(ls, ';')); + check_match(ls, '}', '{', line); + lastlistfield(fs, &cc); + SETARG_B(fs->f->code[pc], ktapc_int2fb(cc.na)); /* set initial array size */ + SETARG_C(fs->f->code[pc], ktapc_int2fb(cc.nh)); /* set initial table size */ +} + +/* }====================================================================== */ + +static void parlist(ktap_lexstate *ls) +{ + /* parlist -> [ param { `,' param } ] */ + ktap_funcstate *fs = ls->fs; + ktap_proto *f = fs->f; + int nparams = 0; + f->is_vararg = 0; + + if (ls->t.token != ')') { /* is `parlist' not empty? */ + do { + switch (ls->t.token) { + case TK_NAME: { /* param -> NAME */ + new_localvar(ls, str_checkname(ls)); + nparams++; + break; + } + case TK_DOTS: { /* param -> `...' */ + lex_next(ls); + f->is_vararg = 1; + break; + } + default: + lex_syntaxerror(ls, " or " KTAP_QL("...") " expected"); + } + } while (!f->is_vararg && testnext(ls, ',')); + } + adjustlocalvars(ls, nparams); + f->numparams = (u8)(fs->nactvar); + codegen_reserveregs(fs, fs->nactvar); /* reserve register for parameters */ +} + +static void body(ktap_lexstate *ls, ktap_expdesc *e, int ismethod, int line) +{ + /* body -> `(' parlist `)' block END */ + ktap_funcstate new_fs; + ktap_blockcnt bl; + + new_fs.f = addprototype(ls); + new_fs.f->linedefined = line; + open_func(ls, &new_fs, &bl); + checknext(ls, '('); + if (ismethod) { + new_localvarliteral(ls, "self"); /* create 'self' parameter */ + adjustlocalvars(ls, 1); + } + parlist(ls); + checknext(ls, ')'); + checknext(ls, '{'); + statlist(ls); + new_fs.f->lastlinedefined = ls->linenumber; + checknext(ls, '}'); + //check_match(ls, TK_END, TK_FUNCTION, line); + codeclosure(ls, e); + close_func(ls); +} + +static void func_body_no_args(ktap_lexstate *ls, ktap_expdesc *e, int line) +{ + /* body -> `(' parlist `)' block END */ + ktap_funcstate new_fs; + ktap_blockcnt bl; + + new_fs.f = addprototype(ls); + new_fs.f->linedefined = line; + open_func(ls, &new_fs, &bl); + checknext(ls, '{'); + statlist(ls); + new_fs.f->lastlinedefined = ls->linenumber; + checknext(ls, '}'); + //check_match(ls, TK_END, TK_FUNCTION, line); + codeclosure(ls, e); + close_func(ls); +} + +static int explist(ktap_lexstate *ls, ktap_expdesc *v) +{ + /* explist -> expr { `,' expr } */ + int n = 1; /* at least one expression */ + + expr(ls, v); + while (testnext(ls, ',')) { + codegen_exp2nextreg(ls->fs, v); + expr(ls, v); + n++; + } + return n; +} + +static void funcargs(ktap_lexstate *ls, ktap_expdesc *f, int line) +{ + ktap_funcstate *fs = ls->fs; + ktap_expdesc args; + int base, nparams; + + switch (ls->t.token) { + case '(': { /* funcargs -> `(' [ explist ] `)' */ + lex_next(ls); + if (ls->t.token == ')') /* arg list is empty? */ + args.k = VVOID; + else { + explist(ls, &args); + codegen_setmultret(fs, &args); + } + check_match(ls, ')', '(', line); + break; + } + case '{': { /* funcargs -> constructor */ + constructor(ls, &args); + break; + } + case TK_STRING: { /* funcargs -> STRING */ + codestring(ls, &args, ls->t.seminfo.ts); + lex_next(ls); /* must use `seminfo' before `next' */ + break; + } + default: { + lex_syntaxerror(ls, "function arguments expected"); + } + } + ktap_assert(f->k == VNONRELOC); + base = f->u.info; /* base register for call */ + if (hasmultret(args.k)) + nparams = KTAP_MULTRET; /* open call */ + else { + if (args.k != VVOID) + codegen_exp2nextreg(fs, &args); /* close last argument */ + nparams = fs->freereg - (base+1); + } + init_exp(f, VCALL, codegen_codeABC(fs, OP_CALL, base, nparams+1, 2)); + codegen_fixline(fs, line); + fs->freereg = base+1; /* call remove function and arguments and leaves + (unless changed) one result */ +} + +/* + * {====================================================================== + * Expression parsing + * ======================================================================= + */ +static void primaryexp(ktap_lexstate *ls, ktap_expdesc *v) +{ + /* primaryexp -> NAME | '(' expr ')' */ + switch (ls->t.token) { + case '(': { + int line = ls->linenumber; + + lex_next(ls); + expr(ls, v); + check_match(ls, ')', '(', line); + codegen_dischargevars(ls->fs, v); + return; + } + case TK_NAME: + singlevar(ls, v); + return; + default: + lex_syntaxerror(ls, "unexpected symbol"); + } +} + +static void suffixedexp(ktap_lexstate *ls, ktap_expdesc *v) +{ + /* suffixedexp -> + primaryexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } */ + ktap_funcstate *fs = ls->fs; + int line = ls->linenumber; + + primaryexp(ls, v); + for (;;) { + switch (ls->t.token) { + case '.': { /* fieldsel */ + fieldsel(ls, v); + break; + } + case '[': { /* `[' exp1 `]' */ + ktap_expdesc key; + codegen_exp2anyregup(fs, v); + yindex(ls, &key); + codegen_indexed(fs, v, &key); + break; + } + case ':': { /* `:' NAME funcargs */ + ktap_expdesc key; + lex_next(ls); + checkname(ls, &key); + codegen_self(fs, v, &key); + funcargs(ls, v, line); + break; + } + case '(': case TK_STRING: case '{': { /* funcargs */ + codegen_exp2nextreg(fs, v); + funcargs(ls, v, line); + break; + } + default: + return; + } + } +} + +static void simpleexp(ktap_lexstate *ls, ktap_expdesc *v) +{ + /* simpleexp -> NUMBER | STRING | NIL | TRUE | FALSE | ... | + constructor | FUNCTION body | suffixedexp */ + switch (ls->t.token) { + case TK_NUMBER: { + init_exp(v, VKNUM, 0); + v->u.nval = ls->t.seminfo.r; + break; + } + case TK_STRING: { + codestring(ls, v, ls->t.seminfo.ts); + break; + } + case TK_NIL: { + init_exp(v, VNIL, 0); + break; + } + case TK_TRUE: { + init_exp(v, VTRUE, 0); + break; + } + case TK_FALSE: { + init_exp(v, VFALSE, 0); + break; + } + case TK_DOTS: { /* vararg */ + ktap_funcstate *fs = ls->fs; + check_condition(ls, fs->f->is_vararg, + "cannot use " KTAP_QL("...") " outside a vararg function"); + init_exp(v, VVARARG, codegen_codeABC(fs, OP_VARARG, 0, 1, 0)); + break; + } + case '{': { /* constructor */ + constructor(ls, v); + return; + } + case TK_FUNCTION: { + lex_next(ls); + body(ls, v, 0, ls->linenumber); + return; + } + case TK_ARGEVENT: + init_exp(v, VEVENT, 0); + break; + + case TK_ARGNAME: + init_exp(v, VEVENTNAME, 0); + break; + case TK_ARG1: + case TK_ARG2: + case TK_ARG3: + case TK_ARG4: + case TK_ARG5: + case TK_ARG6: + case TK_ARG7: + case TK_ARG8: + case TK_ARG9: + init_exp(v, VEVENTARG, ls->t.token - TK_ARG1 + 1); + break; + default: { + suffixedexp(ls, v); + return; + } + } + lex_next(ls); +} + +static UnOpr getunopr(int op) +{ + switch (op) { + case TK_NOT: return OPR_NOT; + case '-': return OPR_MINUS; + case '#': return OPR_LEN; + default: return OPR_NOUNOPR; + } +} + +static BinOpr getbinopr(int op) +{ + switch (op) { + case '+': return OPR_ADD; + case '-': return OPR_SUB; + case '*': return OPR_MUL; + case '/': return OPR_DIV; + case '%': return OPR_MOD; + case '^': return OPR_POW; + case TK_CONCAT: return OPR_CONCAT; + case TK_NE: return OPR_NE; + case TK_EQ: return OPR_EQ; + case '<': return OPR_LT; + case TK_LE: return OPR_LE; + case '>': return OPR_GT; + case TK_GE: return OPR_GE; + case TK_AND: return OPR_AND; + case TK_OR: return OPR_OR; + default: return OPR_NOBINOPR; + } +} + +static const struct { + u8 left; /* left priority for each binary operator */ + u8 right; /* right priority */ +} priority[] = { /* ORDER OPR */ + {6, 6}, {6, 6}, {7, 7}, {7, 7}, {7, 7}, /* `+' `-' `*' `/' `%' */ + {10, 9}, {5, 4}, /* ^, .. (right associative) */ + {3, 3}, {3, 3}, {3, 3}, /* ==, <, <= */ + {3, 3}, {3, 3}, {3, 3}, /* !=, >, >= */ + {2, 2}, {1, 1} /* and, or */ +}; + +#define UNARY_PRIORITY 8 /* priority for unary operators */ + +#define leavelevel(ls) (ls->nCcalls--) + +/* + * subexpr -> (simpleexp | unop subexpr) { binop subexpr } + * where `binop' is any binary operator with a priority higher than `limit' + */ +static BinOpr subexpr(ktap_lexstate *ls, ktap_expdesc *v, int limit) +{ + BinOpr op; + UnOpr uop; + + enterlevel(ls); + uop = getunopr(ls->t.token); + if (uop != OPR_NOUNOPR) { + int line = ls->linenumber; + + lex_next(ls); + subexpr(ls, v, UNARY_PRIORITY); + codegen_prefix(ls->fs, uop, v, line); + } else + simpleexp(ls, v); + + /* expand while operators have priorities higher than `limit' */ + op = getbinopr(ls->t.token); + while (op != OPR_NOBINOPR && priority[op].left > limit) { + ktap_expdesc v2; + BinOpr nextop; + int line = ls->linenumber; + + lex_next(ls); + codegen_infix(ls->fs, op, v); + /* read sub-expression with higher priority */ + nextop = subexpr(ls, &v2, priority[op].right); + codegen_posfix(ls->fs, op, v, &v2, line); + op = nextop; + } + leavelevel(ls); + return op; /* return first untreated operator */ +} + +static void expr(ktap_lexstate *ls, ktap_expdesc *v) +{ + subexpr(ls, v, 0); +} + +/* }==================================================================== */ + +/* + * {====================================================================== + * Rules for Statements + * ======================================================================= + */ +static void block(ktap_lexstate *ls) +{ + /* block -> statlist */ + ktap_funcstate *fs = ls->fs; + ktap_blockcnt bl; + + enterblock(fs, &bl, 0); + statlist(ls); + leaveblock(fs); +} + +/* + * structure to chain all variables in the left-hand side of an + * assignment + */ +struct LHS_assign { + struct LHS_assign *prev; + ktap_expdesc v; /* variable (global, local, upvalue, or indexed) */ +}; + +/* + * check whether, in an assignment to an upvalue/local variable, the + * upvalue/local variable is begin used in a previous assignment to a + * table. If so, save original upvalue/local value in a safe place and + * use this safe copy in the previous assignment. + */ +static void check_conflict(ktap_lexstate *ls, struct LHS_assign *lh, ktap_expdesc *v) +{ + ktap_funcstate *fs = ls->fs; + int extra = fs->freereg; /* eventual position to save local variable */ + int conflict = 0; + + for (; lh; lh = lh->prev) { /* check all previous assignments */ + if (lh->v.k == VINDEXED) { /* assigning to a table? */ + /* table is the upvalue/local being assigned now? */ + if (lh->v.u.ind.vt == v->k && lh->v.u.ind.t == v->u.info) { + conflict = 1; + lh->v.u.ind.vt = VLOCAL; + lh->v.u.ind.t = extra; /* previous assignment will use safe copy */ + } + /* index is the local being assigned? (index cannot be upvalue) */ + if (v->k == VLOCAL && lh->v.u.ind.idx == v->u.info) { + conflict = 1; + lh->v.u.ind.idx = extra; /* previous assignment will use safe copy */ + } + } + } + if (conflict) { + /* copy upvalue/local value to a temporary (in position 'extra') */ + OpCode op = (v->k == VLOCAL) ? OP_MOVE : OP_GETUPVAL; + codegen_codeABC(fs, op, extra, v->u.info, 0); + codegen_reserveregs(fs, 1); + } +} + +static void assignment(ktap_lexstate *ls, struct LHS_assign *lh, int nvars) +{ + ktap_expdesc e; + + check_condition(ls, vkisvar(lh->v.k), "syntax error"); + if (testnext(ls, ',')) { /* assignment -> ',' suffixedexp assignment */ + struct LHS_assign nv; + + nv.prev = lh; + suffixedexp(ls, &nv.v); + if (nv.v.k != VINDEXED) + check_conflict(ls, lh, &nv.v); + checklimit(ls->fs, nvars + ls->nCcalls, KTAP_MAXCCALLS, + "C levels"); + assignment(ls, &nv, nvars+1); + } else if (testnext(ls, '=')) { /* assignment -> '=' explist */ + int nexps; + + nexps = explist(ls, &e); + if (nexps != nvars) { + adjust_assign(ls, nvars, nexps, &e); + /* remove extra values */ + if (nexps > nvars) + ls->fs->freereg -= nexps - nvars; + } else { + /* close last expression */ + codegen_setoneret(ls->fs, &e); + codegen_storevar(ls->fs, &lh->v, &e); + return; /* avoid default */ + } + } else if (testnext(ls, TK_INCR)) { /* assignment -> '+=' explist */ + int nexps; + + nexps = explist(ls, &e); + if (nexps != nvars) { + lex_syntaxerror(ls, "don't allow multi-assign for +="); + } else { + /* close last expression */ + codegen_setoneret(ls->fs, &e); + codegen_storeincr(ls->fs, &lh->v, &e); + return; /* avoid default */ + } + } + + init_exp(&e, VNONRELOC, ls->fs->freereg-1); /* default assignment */ + codegen_storevar(ls->fs, &lh->v, &e); +} + +static int cond(ktap_lexstate *ls) +{ + /* cond -> exp */ + ktap_expdesc v; + expr(ls, &v); /* read condition */ + if (v.k == VNIL) + v.k = VFALSE; /* `falses' are all equal here */ + codegen_goiftrue(ls->fs, &v); + return v.f; +} + +static void gotostat(ktap_lexstate *ls, int pc) +{ + int line = ls->linenumber; + ktap_string *label; + int g; + + if (testnext(ls, TK_GOTO)) + label = str_checkname(ls); + else { + lex_next(ls); /* skip break */ + label = ktapc_ts_new("break"); + } + g = newlabelentry(ls, &ls->dyd->gt, label, line, pc); + findlabel(ls, g); /* close it if label already defined */ +} + +/* check for repeated labels on the same block */ +static void checkrepeated(ktap_funcstate *fs, ktap_labellist *ll, ktap_string *label) +{ + int i; + for (i = fs->bl->firstlabel; i < ll->n; i++) { + if (ktapc_ts_eqstr(label, ll->arr[i].name)) { + const char *msg = ktapc_sprintf( + "label " KTAP_QS " already defined on line %d", + getstr(label), ll->arr[i].line); + semerror(fs->ls, msg); + } + } +} + +/* skip no-op statements */ +static void skipnoopstat(ktap_lexstate *ls) +{ + while (ls->t.token == ';' || ls->t.token == TK_DBCOLON) + statement(ls); +} + +static void labelstat (ktap_lexstate *ls, ktap_string *label, int line) +{ + /* label -> '::' NAME '::' */ + ktap_funcstate *fs = ls->fs; + ktap_labellist *ll = &ls->dyd->label; + int l; /* index of new label being created */ + + checkrepeated(fs, ll, label); /* check for repeated labels */ + checknext(ls, TK_DBCOLON); /* skip double colon */ + /* create new entry for this label */ + l = newlabelentry(ls, ll, label, line, fs->pc); + skipnoopstat(ls); /* skip other no-op statements */ + if (block_follow(ls, 0)) { /* label is last no-op statement in the block? */ + /* assume that locals are already out of scope */ + ll->arr[l].nactvar = fs->bl->nactvar; + } + findgotos(ls, &ll->arr[l]); +} + +static void whilestat(ktap_lexstate *ls, int line) +{ + /* whilestat -> WHILE cond DO block END */ + ktap_funcstate *fs = ls->fs; + int whileinit; + int condexit; + ktap_blockcnt bl; + + lex_next(ls); /* skip WHILE */ + whileinit = codegen_getlabel(fs); + checknext(ls, '('); + condexit = cond(ls); + checknext(ls, ')'); + + enterblock(fs, &bl, 1); + //checknext(ls, TK_DO); + checknext(ls, '{'); + block(ls); + codegen_jumpto(fs, whileinit); + checknext(ls, '}'); + //check_match(ls, TK_END, TK_WHILE, line); + leaveblock(fs); + codegen_patchtohere(fs, condexit); /* false conditions finish the loop */ +} + +static void repeatstat(ktap_lexstate *ls, int line) +{ + /* repeatstat -> REPEAT block UNTIL cond */ + int condexit; + ktap_funcstate *fs = ls->fs; + int repeat_init = codegen_getlabel(fs); + ktap_blockcnt bl1, bl2; + + enterblock(fs, &bl1, 1); /* loop block */ + enterblock(fs, &bl2, 0); /* scope block */ + lex_next(ls); /* skip REPEAT */ + statlist(ls); + check_match(ls, TK_UNTIL, TK_REPEAT, line); + condexit = cond(ls); /* read condition (inside scope block) */ + if (bl2.upval) /* upvalues? */ + codegen_patchclose(fs, condexit, bl2.nactvar); + leaveblock(fs); /* finish scope */ + codegen_patchlist(fs, condexit, repeat_init); /* close the loop */ + leaveblock(fs); /* finish loop */ +} + +static int exp1(ktap_lexstate *ls) +{ + ktap_expdesc e; + int reg; + + expr(ls, &e); + codegen_exp2nextreg(ls->fs, &e); + ktap_assert(e.k == VNONRELOC); + reg = e.u.info; + return reg; +} + +static void forbody(ktap_lexstate *ls, int base, int line, int nvars, int isnum) +{ + /* forbody -> DO block */ + ktap_blockcnt bl; + ktap_funcstate *fs = ls->fs; + int prep, endfor; + + checknext(ls, ')'); + + adjustlocalvars(ls, 3); /* control variables */ + //checknext(ls, TK_DO); + checknext(ls, '{'); + prep = isnum ? codegen_codeAsBx(fs, OP_FORPREP, base, NO_JUMP) : codegen_jump(fs); + enterblock(fs, &bl, 0); /* scope for declared variables */ + adjustlocalvars(ls, nvars); + codegen_reserveregs(fs, nvars); + block(ls); + leaveblock(fs); /* end of scope for declared variables */ + codegen_patchtohere(fs, prep); + if (isnum) /* numeric for? */ + endfor = codegen_codeAsBx(fs, OP_FORLOOP, base, NO_JUMP); + else { /* generic for */ + codegen_codeABC(fs, OP_TFORCALL, base, 0, nvars); + codegen_fixline(fs, line); + endfor = codegen_codeAsBx(fs, OP_TFORLOOP, base + 2, NO_JUMP); + } + codegen_patchlist(fs, endfor, prep + 1); + codegen_fixline(fs, line); +} + +static void fornum(ktap_lexstate *ls, ktap_string *varname, int line) +{ + /* fornum -> NAME = exp1,exp1[,exp1] forbody */ + ktap_funcstate *fs = ls->fs; + int base = fs->freereg; + + new_localvarliteral(ls, "(for index)"); + new_localvarliteral(ls, "(for limit)"); + new_localvarliteral(ls, "(for step)"); + new_localvar(ls, varname); + checknext(ls, '='); + exp1(ls); /* initial value */ + checknext(ls, ','); + exp1(ls); /* limit */ + if (testnext(ls, ',')) + exp1(ls); /* optional step */ + else { /* default step = 1 */ + codegen_codek(fs, fs->freereg, codegen_numberK(fs, 1)); + codegen_reserveregs(fs, 1); + } + forbody(ls, base, line, 1, 1); +} + +static void forlist(ktap_lexstate *ls, ktap_string *indexname) +{ + /* forlist -> NAME {,NAME} IN explist forbody */ + ktap_funcstate *fs = ls->fs; + ktap_expdesc e; + int nvars = 4; /* gen, state, control, plus at least one declared var */ + int line; + int base = fs->freereg; + + /* create control variables */ + new_localvarliteral(ls, "(for generator)"); + new_localvarliteral(ls, "(for state)"); + new_localvarliteral(ls, "(for control)"); + /* create declared variables */ + new_localvar(ls, indexname); + while (testnext(ls, ',')) { + new_localvar(ls, str_checkname(ls)); + nvars++; + } + checknext(ls, TK_IN); + line = ls->linenumber; + adjust_assign(ls, 3, explist(ls, &e), &e); + codegen_checkstack(fs, 3); /* extra space to call generator */ + forbody(ls, base, line, nvars - 3, 0); +} + +static void forstat(ktap_lexstate *ls, int line) +{ + /* forstat -> FOR (fornum | forlist) END */ + ktap_funcstate *fs = ls->fs; + ktap_string *varname; + ktap_blockcnt bl; + + enterblock(fs, &bl, 1); /* scope for loop and control variables */ + lex_next(ls); /* skip `for' */ + + checknext(ls, '('); + varname = str_checkname(ls); /* first variable name */ + switch (ls->t.token) { + case '=': + fornum(ls, varname, line); + break; + case ',': case TK_IN: + forlist(ls, varname); + break; + default: + lex_syntaxerror(ls, KTAP_QL("=") " or " KTAP_QL("in") " expected"); + } + //check_match(ls, TK_END, TK_FOR, line); + checknext(ls, '}'); + leaveblock(fs); /* loop scope (`break' jumps to this point) */ +} + +static void test_then_block(ktap_lexstate *ls, int *escapelist) +{ + /* test_then_block -> [IF | ELSEIF] cond THEN block */ + ktap_blockcnt bl; + ktap_funcstate *fs = ls->fs; + ktap_expdesc v; + int jf; /* instruction to skip 'then' code (if condition is false) */ + + lex_next(ls); /* skip IF or ELSEIF */ + checknext(ls, '('); + expr(ls, &v); /* read condition */ + checknext(ls, ')'); + //checknext(ls, TK_THEN); + checknext(ls, '{'); + if (ls->t.token == TK_GOTO || ls->t.token == TK_BREAK) { + codegen_goiffalse(ls->fs, &v); /* will jump to label if condition is true */ + enterblock(fs, &bl, 0); /* must enter block before 'goto' */ + gotostat(ls, v.t); /* handle goto/break */ + skipnoopstat(ls); /* skip other no-op statements */ + if (block_follow(ls, 0)) { /* 'goto' is the entire block? */ + leaveblock(fs); + checknext(ls, '}'); + return; /* and that is it */ + } else /* must skip over 'then' part if condition is false */ + jf = codegen_jump(fs); + } else { /* regular case (not goto/break) */ + codegen_goiftrue(ls->fs, &v); /* skip over block if condition is false */ + enterblock(fs, &bl, 0); + jf = v.f; + } + statlist(ls); /* `then' part */ + checknext(ls, '}'); + leaveblock(fs); + if (ls->t.token == TK_ELSE || ls->t.token == TK_ELSEIF) /* followed by 'else'/'elseif'? */ + codegen_concat(fs, escapelist, codegen_jump(fs)); /* must jump over it */ + codegen_patchtohere(fs, jf); +} + +static void ifstat(ktap_lexstate *ls, int line) +{ + /* ifstat -> IF cond THEN block {ELSEIF cond THEN block} [ELSE block] END */ + ktap_funcstate *fs = ls->fs; + int escapelist = NO_JUMP; /* exit list for finished parts */ + + test_then_block(ls, &escapelist); /* IF cond THEN block */ + while (ls->t.token == TK_ELSEIF) + test_then_block(ls, &escapelist); /* ELSEIF cond THEN block */ + if (testnext(ls, TK_ELSE)) { + checknext(ls, '{'); + block(ls); /* `else' part */ + checknext(ls, '}'); + } + //check_match(ls, TK_END, TK_IF, line); + codegen_patchtohere(fs, escapelist); /* patch escape list to 'if' end */ +} + +static void localfunc(ktap_lexstate *ls) +{ + ktap_expdesc b; + ktap_funcstate *fs = ls->fs; + + new_localvar(ls, str_checkname(ls)); /* new local variable */ + adjustlocalvars(ls, 1); /* enter its scope */ + body(ls, &b, 0, ls->linenumber); /* function created in next register */ + /* debug information will only see the variable after this point! */ + getlocvar(fs, b.u.info)->startpc = fs->pc; +} + +static void localstat(ktap_lexstate *ls) +{ + /* stat -> LOCAL NAME {`,' NAME} [`=' explist] */ + int nvars = 0; + int nexps; + ktap_expdesc e; + + do { + new_localvar(ls, str_checkname(ls)); + nvars++; + } while (testnext(ls, ',')); + if (testnext(ls, '=')) + nexps = explist(ls, &e); + else { + e.k = VVOID; + nexps = 0; + } + adjust_assign(ls, nvars, nexps, &e); + adjustlocalvars(ls, nvars); +} + +static int funcname(ktap_lexstate *ls, ktap_expdesc *v) +{ + /* funcname -> NAME {fieldsel} [`:' NAME] */ + int ismethod = 0; + + singlevar(ls, v); + while (ls->t.token == '.') + fieldsel(ls, v); + if (ls->t.token == ':') { + ismethod = 1; + fieldsel(ls, v); + } + return ismethod; +} + +static void funcstat(ktap_lexstate *ls, int line) +{ + /* funcstat -> FUNCTION funcname body */ + int ismethod; + ktap_expdesc v, b; + + lex_next(ls); /* skip FUNCTION */ + ismethod = funcname(ls, &v); + body(ls, &b, ismethod, line); + codegen_storevar(ls->fs, &v, &b); + codegen_fixline(ls->fs, line); /* definition `happens' in the first line */ +} + +static void exprstat(ktap_lexstate *ls) +{ + /* stat -> func | assignment */ + ktap_funcstate *fs = ls->fs; + struct LHS_assign v; + + suffixedexp(ls, &v.v); + /* stat -> assignment ? */ + if (ls->t.token == '=' || ls->t.token == ',' || + ls->t.token == TK_INCR) { + v.prev = NULL; + assignment(ls, &v, 1); + } else { /* stat -> func */ + check_condition(ls, v.v.k == VCALL, "syntax error"); + SETARG_C(getcode(fs, &v.v), 1); /* call statement uses no results */ + } +} + +static void retstat(ktap_lexstate *ls) +{ + /* stat -> RETURN [explist] [';'] */ + ktap_funcstate *fs = ls->fs; + ktap_expdesc e; + int first, nret; /* registers with returned values */ + + if (block_follow(ls, 1) || ls->t.token == ';') + first = nret = 0; /* return no values */ + else { + nret = explist(ls, &e); /* optional return values */ + if (hasmultret(e.k)) { + codegen_setmultret(fs, &e); + if (e.k == VCALL && nret == 1) { /* tail call? */ + SET_OPCODE(getcode(fs,&e), OP_TAILCALL); + ktap_assert(GETARG_A(getcode(fs,&e)) == fs->nactvar); + } + first = fs->nactvar; + nret = KTAP_MULTRET; /* return all values */ + } else { + if (nret == 1) /* only one single value? */ + first = codegen_exp2anyreg(fs, &e); + else { + codegen_exp2nextreg(fs, &e); /* values must go to the `stack' */ + first = fs->nactvar; /* return all `active' values */ + ktap_assert(nret == fs->freereg - first); + } + } + } + codegen_ret(fs, first, nret); + testnext(ls, ';'); /* skip optional semicolon */ +} + +static void tracestat(ktap_lexstate *ls) +{ + ktap_expdesc v0, key, args; + ktap_expdesc *v = &v0; + ktap_string *kdebug_str = ktapc_ts_new("kdebug"); + ktap_string *probe_str = ktapc_ts_new("probe_by_id"); + ktap_string *probe_end_str = ktapc_ts_new("probe_end"); + ktap_funcstate *fs = ls->fs; + int token = ls->t.token; + int line = ls->linenumber; + int base, nparams; + + if (token == TK_TRACE) + lex_read_string_until(ls, '{'); + else + lex_next(ls); /* skip "trace_end" keyword */ + + /* kdebug */ + singlevaraux(fs, ls->envn, v, 1); /* get environment variable */ + codestring(ls, &key, kdebug_str); /* key is variable name */ + codegen_indexed(fs, v, &key); /* env[varname] */ + + /* fieldsel: kdebug.probe */ + codegen_exp2anyregup(fs, v); + if (token == TK_TRACE) + codestring(ls, &key, probe_str); + else if (token == TK_TRACE_END) + codestring(ls, &key, probe_end_str); + codegen_indexed(fs, v, &key); + + /* funcargs*/ + codegen_exp2nextreg(fs, v); + + if (token == TK_TRACE) { + /* argument: EVENTDEF string */ + check(ls, TK_STRING); + enterlevel(ls); + ktap_string *ts = ktapc_parse_eventdef(ls->t.seminfo.ts); + check_condition(ls, ts != NULL, "Cannot parse eventdef"); + codestring(ls, &args, ts); + lex_next(ls); /* skip EVENTDEF string */ + leavelevel(ls); + + codegen_exp2nextreg(fs, &args); /* for next argument */ + } + + /* argument: callback function */ + enterlevel(ls); + func_body_no_args(ls, &args, ls->linenumber); + leavelevel(ls); + + codegen_setmultret(fs, &args); + + base = v->u.info; /* base register for call */ + if (hasmultret(args.k)) + nparams = KTAP_MULTRET; /* open call */ + else { + codegen_exp2nextreg(fs, &args); /* close last argument */ + nparams = fs->freereg - (base+1); + } + init_exp(v, VCALL, codegen_codeABC(fs, OP_CALL, base, nparams+1, 2)); + codegen_fixline(fs, line); + fs->freereg = base+1; + + check_condition(ls, v->k == VCALL, "syntax error"); + SETARG_C(getcode(fs, v), 1); /* call statement uses no results */ +} + +static void timerstat(ktap_lexstate *ls) +{ + ktap_expdesc v0, key, args; + ktap_expdesc *v = &v0; + ktap_funcstate *fs = ls->fs; + ktap_string *token_str = ls->t.seminfo.ts; + ktap_string *interval_str; + int line = ls->linenumber; + int base, nparams; + + lex_next(ls); /* skip profile/tick keyword */ + check(ls, '-'); + + lex_read_string_until(ls, '{'); + interval_str = ls->t.seminfo.ts; + + //printf("timerstat str: %s\n", getstr(interval_str)); + //exit(0); + + /* timer */ + singlevaraux(fs, ls->envn, v, 1); /* get environment variable */ + codestring(ls, &key, ktapc_ts_new("timer")); /* key is variable name */ + codegen_indexed(fs, v, &key); /* env[varname] */ + + /* fieldsel: timer.profile, timer.tick */ + codegen_exp2anyregup(fs, v); + codestring(ls, &key, token_str); + codegen_indexed(fs, v, &key); + + /* funcargs*/ + codegen_exp2nextreg(fs, v); + + /* argument: interval string */ + check(ls, TK_STRING); + enterlevel(ls); + codestring(ls, &args, interval_str); + lex_next(ls); /* skip interval string */ + leavelevel(ls); + + codegen_exp2nextreg(fs, &args); /* for next argument */ + + /* argument: callback function */ + enterlevel(ls); + func_body_no_args(ls, &args, ls->linenumber); + leavelevel(ls); + + codegen_setmultret(fs, &args); + + base = v->u.info; /* base register for call */ + if (hasmultret(args.k)) + nparams = KTAP_MULTRET; /* open call */ + else { + codegen_exp2nextreg(fs, &args); /* close last argument */ + nparams = fs->freereg - (base+1); + } + init_exp(v, VCALL, codegen_codeABC(fs, OP_CALL, base, nparams+1, 2)); + codegen_fixline(fs, line); + fs->freereg = base+1; + + check_condition(ls, v->k == VCALL, "syntax error"); + SETARG_C(getcode(fs, v), 1); /* call statement uses no results */ +} + +static void statement(ktap_lexstate *ls) +{ + int line = ls->linenumber; /* may be needed for error messages */ + + enterlevel(ls); + switch (ls->t.token) { + case ';': { /* stat -> ';' (empty statement) */ + lex_next(ls); /* skip ';' */ + break; + } + case TK_IF: { /* stat -> ifstat */ + ifstat(ls, line); + break; + } + case TK_WHILE: { /* stat -> whilestat */ + whilestat(ls, line); + break; + } + case TK_DO: { /* stat -> DO block END */ + lex_next(ls); /* skip DO */ + block(ls); + check_match(ls, TK_END, TK_DO, line); + break; + } + case TK_FOR: { /* stat -> forstat */ + forstat(ls, line); + break; + } + case TK_REPEAT: { /* stat -> repeatstat */ + repeatstat(ls, line); + break; + } + case TK_FUNCTION: { /* stat -> funcstat */ + funcstat(ls, line); + break; + } + case TK_LOCAL: { /* stat -> localstat */ + lex_next(ls); /* skip LOCAL */ + if (testnext(ls, TK_FUNCTION)) /* local function? */ + localfunc(ls); + else + localstat(ls); + break; + } + case TK_DBCOLON: { /* stat -> label */ + lex_next(ls); /* skip double colon */ + labelstat(ls, str_checkname(ls), line); + break; + } + case TK_RETURN: { /* stat -> retstat */ + lex_next(ls); /* skip RETURN */ + retstat(ls); + break; + } + case TK_BREAK: /* stat -> breakstat */ + case TK_GOTO: { /* stat -> 'goto' NAME */ + gotostat(ls, codegen_jump(ls->fs)); + break; + } + + case TK_TRACE: + case TK_TRACE_END: + tracestat(ls); + break; + case TK_PROFILE: + case TK_TICK: + timerstat(ls); + break; + default: { /* stat -> func | assignment */ + exprstat(ls); + break; + } + } + //ktap_assert(ls->fs->f->maxstacksize >= ls->fs->freereg && + // ls->fs->freereg >= ls->fs->nactvar); + ls->fs->freereg = ls->fs->nactvar; /* free registers */ + leavelevel(ls); +} +/* }====================================================================== */ + +/* + * compiles the main function, which is a regular vararg function with an upvalue + */ +static void mainfunc(ktap_lexstate *ls, ktap_funcstate *fs) +{ + ktap_blockcnt bl; + ktap_expdesc v; + + open_func(ls, fs, &bl); + fs->f->is_vararg = 1; /* main function is always vararg */ + init_exp(&v, VLOCAL, 0); /* create and... */ + newupvalue(fs, ls->envn, &v); /* ...set environment upvalue */ + lex_next(ls); /* read first token */ + statlist(ls); /* parse main body */ + check(ls, TK_EOS); + close_func(ls); +} + +ktap_closure *ktapc_parser(char *ptr, const char *name) +{ + ktap_lexstate lexstate; + ktap_funcstate funcstate; + ktap_dyndata dyd; + ktap_mbuffer buff; + int firstchar = *ptr++; + ktap_closure *cl = ktapc_newlclosure(1); /* create main closure */ + + memset(&lexstate, 0, sizeof(ktap_lexstate)); + memset(&funcstate, 0, sizeof(ktap_funcstate)); + funcstate.f = cl->l.p = ktapc_newproto(); + funcstate.f->source = ktapc_ts_new(name); /* create and anchor ktap_string */ + + lex_init(); + + mbuff_init(&buff); + memset(&dyd, 0, sizeof(ktap_dyndata)); + lexstate.buff = &buff; + lexstate.dyd = &dyd; + lex_setinput(&lexstate, ptr, funcstate.f->source, firstchar); + + mainfunc(&lexstate, &funcstate); + + ktap_assert(!funcstate.prev && funcstate.nups == 1 && !lexstate.fs); + + /* all scopes should be correctly finished */ + ktap_assert(dyd.actvar.n == 0 && dyd.gt.n == 0 && dyd.label.n == 0); + return cl; +} + diff --git a/drivers/staging/ktap/userspace/util.c b/drivers/staging/ktap/userspace/util.c new file mode 100644 index 000000000000..8124de995be7 --- /dev/null +++ b/drivers/staging/ktap/userspace/util.c @@ -0,0 +1,346 @@ +/* + * util.c + * + * This file is part of ktap by Jovi Zhangwei. + * + * Copyright (C) 2012-2013 Jovi Zhangwei . + * + * Copyright (C) 1994-2013 Lua.org, PUC-Rio. + * - The part of code in this file is copied from lua initially. + * - lua's MIT license is compatible with GPL. + * + * ktap is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * ktap is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include "../include/ktap_types.h" +#include "ktapc.h" + +/* + * converts an integer to a "floating point byte", represented as + * (eeeeexxx), where the real value is (1xxx) * 2^(eeeee - 1) if + * eeeee != 0 and (xxx) otherwise. + */ +int ktapc_int2fb(unsigned int x) +{ + int e = 0; /* exponent */ + + if (x < 8) + return x; + while (x >= 0x10) { + x = (x+1) >> 1; + e++; + } + return ((e+1) << 3) | ((int)x - 8); +} + +/* converts back */ +int ktapc_fb2int(int x) +{ + int e = (x >> 3) & 0x1f; + + if (e == 0) + return x; + else + return ((x & 7) + 8) << (e - 1); +} + +int ktapc_ceillog2(unsigned int x) +{ + static const u8 log_2[256] = { + 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 + }; + int l = 0; + + x--; + while (x >= 256) { + l += 8; + x >>= 8; + } + return l + log_2[x]; +} + +ktap_number ktapc_arith(int op, ktap_number v1, ktap_number v2) +{ + switch (op) { + case KTAP_OPADD: return NUMADD(v1, v2); + case KTAP_OPSUB: return NUMSUB(v1, v2); + case KTAP_OPMUL: return NUMMUL(v1, v2); + case KTAP_OPDIV: return NUMDIV(v1, v2); + case KTAP_OPMOD: return NUMMOD(v1, v2); + //case KTAP_OPPOW: return NUMPOW(v1, v2); + case KTAP_OPUNM: return NUMUNM(v1); + default: ktap_assert(0); return 0; + } +} + +int ktapc_hexavalue(int c) +{ + if (isdigit(c)) + return c - '0'; + else + return tolower(c) - 'a' + 10; +} + +static int isneg(const char **s) +{ + if (**s == '-') { + (*s)++; + return 1; + } else if (**s == '+') + (*s)++; + + return 0; +} + +static ktap_number readhexa(const char **s, ktap_number r, int *count) +{ + for (; isxdigit((unsigned char)(**s)); (*s)++) { /* read integer part */ + r = (r * 16.0) + (ktap_number)(ktapc_hexavalue((unsigned char)(**s))); + (*count)++; + } + + return r; +} + +/* + * convert an hexadecimal numeric string to a number, following + * C99 specification for 'strtod' + */ +static ktap_number strx2number(const char *s, char **endptr) +{ + ktap_number r = 0.0; + int e = 0, i = 0; + int neg = 0; /* 1 if number is negative */ + + *endptr = (char *)s; /* nothing is valid yet */ + while (isspace((unsigned char)(*s))) + s++; /* skip initial spaces */ + + neg = isneg(&s); /* check signal */ + if (!(*s == '0' && (*(s + 1) == 'x' || *(s + 1) == 'X'))) /* check '0x' */ + return 0.0; /* invalid format (no '0x') */ + + s += 2; /* skip '0x' */ + r = readhexa(&s, r, &i); /* read integer part */ + if (*s == '.') { + s++; /* skip dot */ + r = readhexa(&s, r, &e); /* read fractional part */ + } + + if (i == 0 && e == 0) + return 0.0; /* invalid format (no digit) */ + e *= -4; /* each fractional digit divides value by 2^-4 */ + *endptr = (char *)s; /* valid up to here */ + + if (*s == 'p' || *s == 'P') { /* exponent part? */ + int exp1 = 0; + int neg1; + + s++; /* skip 'p' */ + neg1 = isneg(&s); /* signal */ + if (!isdigit((unsigned char)(*s))) + goto ret; /* must have at least one digit */ + while (isdigit((unsigned char)(*s))) /* read exponent */ + exp1 = exp1 * 10 + *(s++) - '0'; + if (neg1) exp1 = -exp1; + e += exp1; + } + + *endptr = (char *)s; /* valid up to here */ + ret: + if (neg) + r = -r; + + return ldexp(r, e); +} + +int ktapc_str2d(const char *s, size_t len, ktap_number *result) +{ + char *endptr; + + if (strpbrk(s, "nN")) /* reject 'inf' and 'nan' */ + return 0; + else if (strpbrk(s, "xX")) /* hexa? */ + *result = strx2number(s, &endptr); + else + *result = strtod(s, &endptr); + + if (endptr == s) + return 0; /* nothing recognized */ + while (isspace((unsigned char)(*endptr))) + endptr++; + return (endptr == s + len); /* OK if no trailing characters */ +} + +/* number of chars of a literal string without the ending \0 */ +#define LL(x) (sizeof(x)/sizeof(char) - 1) + +#define RETS "..." +#define PRE "[string \"" +#define POS "\"]" + +#define addstr(a,b,l) ( memcpy(a,b,(l) * sizeof(char)), a += (l) ) + +void ktapc_chunkid(char *out, const char *source, size_t bufflen) +{ + size_t l = strlen(source); + + if (*source == '=') { /* 'literal' source */ + if (l <= bufflen) /* small enough? */ + memcpy(out, source + 1, l * sizeof(char)); + else { /* truncate it */ + addstr(out, source + 1, bufflen - 1); + *out = '\0'; + } + } else if (*source == '@') { /* file name */ + if (l <= bufflen) /* small enough? */ + memcpy(out, source + 1, l * sizeof(char)); + else { /* add '...' before rest of name */ + addstr(out, RETS, LL(RETS)); + bufflen -= LL(RETS); + memcpy(out, source + 1 + l - bufflen, bufflen * sizeof(char)); + } + } else { /* string; format as [string "source"] */ + const char *nl = strchr(source, '\n'); /* find first new line (if any) */ + addstr(out, PRE, LL(PRE)); /* add prefix */ + bufflen -= LL(PRE RETS POS) + 1; /* save space for prefix+suffix+'\0' */ + if (l < bufflen && nl == NULL) { /* small one-line source? */ + addstr(out, source, l); /* keep it */ + } else { + if (nl != NULL) + l = nl - source; /* stop at first newline */ + if (l > bufflen) + l = bufflen; + addstr(out, source, l); + addstr(out, RETS, LL(RETS)); + } + memcpy(out, POS, (LL(POS) + 1) * sizeof(char)); + } +} + + +/* + * strglobmatch is copyed from perf(linux/tools/perf/util/string.c) + */ + +/* Character class matching */ +static bool __match_charclass(const char *pat, char c, const char **npat) +{ + bool complement = false, ret = true; + + if (*pat == '!') { + complement = true; + pat++; + } + if (*pat++ == c) /* First character is special */ + goto end; + + while (*pat && *pat != ']') { /* Matching */ + if (*pat == '-' && *(pat + 1) != ']') { /* Range */ + if (*(pat - 1) <= c && c <= *(pat + 1)) + goto end; + if (*(pat - 1) > *(pat + 1)) + goto error; + pat += 2; + } else if (*pat++ == c) + goto end; + } + if (!*pat) + goto error; + ret = false; + +end: + while (*pat && *pat != ']') /* Searching closing */ + pat++; + if (!*pat) + goto error; + *npat = pat + 1; + return complement ? !ret : ret; + +error: + return false; +} + +/* Glob/lazy pattern matching */ +static bool __match_glob(const char *str, const char *pat, bool ignore_space) +{ + while (*str && *pat && *pat != '*') { + if (ignore_space) { + /* Ignore spaces for lazy matching */ + if (isspace(*str)) { + str++; + continue; + } + if (isspace(*pat)) { + pat++; + continue; + } + } + if (*pat == '?') { /* Matches any single character */ + str++; + pat++; + continue; + } else if (*pat == '[') /* Character classes/Ranges */ + if (__match_charclass(pat + 1, *str, &pat)) { + str++; + continue; + } else + return false; + else if (*pat == '\\') /* Escaped char match as normal char */ + pat++; + if (*str++ != *pat++) + return false; + } + /* Check wild card */ + if (*pat == '*') { + while (*pat == '*') + pat++; + if (!*pat) /* Tail wild card matches all */ + return true; + while (*str) + if (__match_glob(str++, pat, ignore_space)) + return true; + } + return !*str && !*pat; +} + +/** + * strglobmatch - glob expression pattern matching + * @str: the target string to match + * @pat: the pattern string to match + * + * This returns true if the @str matches @pat. @pat can includes wildcards + * ('*','?') and character classes ([CHARS], complementation and ranges are + * also supported). Also, this supports escape character ('\') to use special + * characters as normal character. + * + * Note: if @pat syntax is broken, this always returns false. + */ +bool strglobmatch(const char *str, const char *pat) +{ + return __match_glob(str, pat, false); +} +