2.3 Sistema operativo de la tarjeta BeagleBoard y módulos software añadidos
2.3.3 Estándar de vídeo H.264/SVC
As evident from baseline results in Figure 7.6, the number of extra CSr sections cause almost
the exact same number of access faults. This is therefore the absolute minimum overhead that FlowJIT causes. Therefore, in order to quantify this per access-fault overhead in execution, we devised a synthetic experiment that performs 20 thousand small code compilations and subsequently executes them. We then measure precisely in-kernel how much time a single FlowJIT event takes. For each execution, the overhead T can be quantified as,
T = ttr+ tac+ tpf
Here, ttr denotes the tracking initiation time, tac denotes page access change time and tpf
denotes the access page fault time. Out of these, ttr can be safely neglected as the tracking
initiation is a one time cost and would be insignificant over recurring tac + tpf time in our
experiment. Therefore, at runtime, FlowJIT’s addition to the critical execution path time now is (1) modifying access permissions ; (2) allocate memory for FlowJIT event ; (3) obtain and copy timestamp and instruction pointer ; (4) copy the modified page and (5) add the FlowJIT event to the linked list. This is the flow that is of interest to us. We label this as revised overhead (Tobs = tac+ tpf) and monitor it by putting timestamps for the complete
Tobs flow inside our FlowJIT kernel implementation.
We plotted the density curve for the 20 thousand executions, with the observed overhead per access fault and page dump time (Tobs) as a parameter. As observed in Figure 7.8, the
overhead of FlowJIT varied in our synthetic tests from 973ns to 6.8µs with three distinct peaks at 1.3µs, 2.7µs and 6.6µs. This multi-modal variation was a result of the non-uniform nature of page-faults themselves, due to the caching effects related to VMA resolution and subsequent TLB hits and misses. To minimize variations as much as possible, and ensuring that the observations were reproducible, we isolated page faults to just those access faults which were caused by our FlowJIT tracked pages. We can thus infer that, in an ideal scenario, most of the page faults took around 1.3µs. The individual values of tac obtained in our tests
had an average of 20.8ns, which was very low compared to tpf. Overall, the total cost is
almost negligible, considering the huge number of page faults that naturally occur in the due course of process execution.
Figure 7.8 Distribution of overhead for each access fault
The current implementation of FlowJIT requires copying tracked pages between kernel and userspace. In future versions, we plan to reduce the overhead further to a few nanoseconds range by userspace-kernel shared memory implementation or the splice() technique used in [11], thus minimizing kernel-userspace copy costs. For our current implementation, the event copy cost is part of the tpf overhead discussed before. We confirmed that our observations are
in line with our synthetic tests of memcpy(), where 349ns were required on the test system for copying data of similar size as a single FlowJIT event.
7.7 Conclusion
Recent advancements in hardware tracing allow compact and accurate branch traces to be generated for a target process or OS kernel. However, we observed that decoding the traces post-execution either fails or produces erroneous instruction flow when runtime compiled code is generated and executed by the target. To overcome this problem, we developed FlowJIT, a kernel-assisted technique that allows tracking of pages containing runtime generated code, and dumping them for rectifying and reconstructing the hardware trace. Our OS-centric approach is language and JIT compiler agnostic, thus removes the reliance on assistance from their infrastructure. Our technique allows accurate instruction flow reconstruction, with a negligible cost of around 1.3µs of extra execution overhead per access fault event. FlowJIT solves one of the major flaws in dynamically compiled code reconstruction that has not yet been tackled until now. This approach with minimal overhead makes hardware tracing more robust and reliable.
Even though we cover the two major cases for runtime generated code – method based JIT compilation and direct code instrumentation, a natural extension of this work would be to cover the special case of self-modifying code as well. Currently, such an analysis would require single-stepping instructions in the target process to assess if they are being rewritten while recording individual changes. As we observed in Subsection 7.5.2, the modifications within the same page could indeed be observed by FlowJIT. This may induce considerable overhead but open up larger avenues for code security analysis using hardware traces. Another extension to our technique may be to identify and track all executable pages of the process, thus eliminating the need to rely on on-disk process binaries for code reconstruction. This would allow faster and efficient decoding and instruction profiling, but with a trade-off of not having function names at analysis time, since this is stored in extra sections in the binaries on disk.