我们提到过用Trampoline可以heap换stack,以遍历数据结构代替递归运算来实现运行安全。那么什么是Trampoline呢? 1 sealed trait Trampoline[+A] 2 case class Done[A](a: A) extends Trampoline[A] 3 case class More[A]( k: () => Trampoline[A]) extends Trampoline[A] Trampoline就是一种数据结构。 它有两种状态:Done(a: A)代表结构内存放了一个A值,More(k: ()=>Trampoline[A])代表结构内存放的是一个Function0函数,就是一个运算Trampoline[A]。 我们可以把scalaz的Trampoline用在even,odd函数里: 1 import scalaz.Free.Trampoline 2 def even(xa: List[Int]): Trampoline
[A] 8 case class More[+A](k: () => Trampoline[A]) extends Trampoline[A] Trampoline代表一个可以一步步进行的运算。 有了Trampoline我们可以把even,odd的函数类型换成Trampoline: 1 def even[A](as: List[A]): Trampoline[Boolean] = as match FlatMap[A,B](sub: Trampoline[A], k: A => Trampoline[B]) extends Trampoline[B] case class FlatMap这种Trampoline : Trampoline[A], k: A => Trampoline[B]) extends Trampoline[B] 在以上对Trampoline的调整里我们引用了Monad的结合特性(associativity : Trampoline[A], k: A => Trampoline[B]) extends Trampoline[B] 我们可以用这个zip函数把几个Trampoline运算交叉组合起来实现并行运算
跳转到STVEC指向的代码,也就是trampoline page中,这个是从supervisor mode返回user mode时设置的。 图片3 trampoline.uservec之前讲解过用户页表的布局,虚拟地址最高处的几页比较特殊,trampoline是trap代码,系统调用进入内核的必经之路,此时会执行trampoline.S中uservec 代码进行保存用户寄存器到trapframe,并将trapframe中的内核参数设置到指定寄存器,trampoline这一页每个进程都有,共享同一个物理页,但是trampoline这一页没有PTE_U,不能被用户态执行 intr_off(); // send syscalls, interrupts, and exceptions to trampoline.S w_stvec(TRAMPOLINE + (uservec uint64 fn = TRAMPOLINE + (userret - trampoline);//执行trampoline.userret代码段,这两个参数分别放到a0、a1寄存器中 ((void
trampoline在内核页表、用户页表都有,是用于用户态和内核态切换的,涉及到页表地址的设置。 kvmmap(kpgtbl, TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X); // map kernel stacks proc_mapstacks trampoline是trampoline.S文件的字符数组映射到虚拟地址最高端用于内陷。 //用户页表最高处是trampoline,用于中断,不属于用户 if(mappages(pagetable, TRAMPOLINE, PGSIZE, (uint64)trampoline , for trampoline.S.
在上节我们介绍了Trampoline。它主要是为了解决堆栈溢出(StackOverflow)错误而设计的。 Trampoline类型是一种数据结构,它的设计思路是以heap换stack:对应传统递归算法运行时在堆栈上寄存程序状态,用Trampoline进行递归算法时程序状态是保存在Trampoline的数据结构里的 所以IO算法设计也会采用与Trampoline一样的数据结构。或者我们应该沿用Trampoline数据结构和算法来设计IO组件库。如此思考那么我们就必须对Trampoline进行深度抽象了。 ,<function1>) 7 8 prg.foldMap(TestConsole) 在上一节我们讨论了Trampoline。主要目的是解决泛函算法中不可避免的堆栈溢出问题。 我们应该考虑在Free Monad里使用Trampoline类型。这样我们才可以放心地用Free Monad来产生任何类型的Monad并在运算中以heap换stack解决堆栈溢出问题。
struct.pack("<L", page[0] + offset) tramp += "\xff\xe3" if trampoline_offset ] <= equals_button and equals_button < (page[0] + page[1] -7): print "[*] Found our trampoline = physical+ v_offset print "[*] Found our trampoline target at: 0x%08x" % (trampoline_offset if slack_space is not None: break print "[*] Writing trampoline fd = open(memory_file, "r+") fd.seek(trampoline_offset) fd.write(tramp) fd.close
fn函数是就是刚刚我向你展示的位于trampoline.S中的代码。 程序现在仍然在trampoline的最开始,也就是uservec函数的最开始,我们基本上还没有执行任何内容。 因为我们还在trampoline代码中,而trampoline代码在用户空间和内核空间都映射到了同一个地址。 在下一行我们设置了STVEC寄存器指向trampoline代码,在那里最终会执行sret指令返回到用户空间。位于trampoline代码最后的sret指令会重新打开中断。 为什么trampoline代码中不保存SEPC寄存器? trampoline代码没有像其他寄存器一样保存这个寄存器,但是非常欢迎大家修改XV6来保存它。 实际上,我们会在汇编代码trampoline中完成page table的切换,并且也只能在trampoline中完成切换,因为只有trampoline中代码是同时在用户和内核空间中映射。
前面我们介绍了Trampoline的运算模式可以有效解决堆栈溢出问题,而上节的Free Monad介绍里还没有把Free Monad与Trampoline运算模式挂上钩。 我们先考虑一下如何在Free Monad数据类型里引入Trampoline运算模式。 [A] { 14 private case class FlatMap[B](a: Trampoline[A], f: A => Trampoline[B]) extends Trampoline[ [A] 38 case class More[A](k: () => Trampoline[A]) extends Trampoline[A] 这两个数据类型的设计目的都是为了能逐步运行算法:按照算法运算的状态确定下一步该如何运行 为了实现Free Monad在运行中采用Trampoline运行机制,我们可以像Trampoline数据类型一样来实现resume,这个确定每一步运算方式的函数: 1 trait Free[F[_],
LPVOID pTrampoline; // Address of the trampoline function. LPVOID pTrampoline; // [In] Buffer address for the trampoline and relay function. UINT8 newIPs[8]; // [Out] Instruction boundaries of the trampoline function. } TRAMPOLINE, *PTRAMPOLINE = hs.len) return FALSE; // Trampoline function is too large. if ((newPos + copySize) > TRAMPOLINE_MAX_SIZE) return FALSE; // Trampoline function
. // 为进程的用户态页表分配一个新的空闲物理页--同时做好TRAMPOLINE和TRAMPOLINE的映射 // TRAMPOLINE和TRAMPOLINE这两部分代码是上下文切换通用代码, usertrapret函数中会执行S态返回用户态的操作: //proc.c //这三个外部全局遍历定义在trampoline.s中 extern char trampoline[], uservec[ (TRAPFRAME, satp); } trampoline,uservec,userret是定义在trampoline.S中的三个全局符号,其中trampoline 符号是一个占位符标记,并不包含任何指令地址 xv6使用包含uservec的蹦床页面(trampoline page)来满足这些约束。xv6将蹦床页面映射到内核页表和每个用户页表中相同的虚拟地址。这个虚拟地址是TRAMPOLINE。 蹦床内容在trampoline.S中设置,并且(当执行用户代码时)stvec设置为uservec (kernel/trampoline.S:16)。
好在天无绝人之路,仔细阅读维基百科中关于尾调用的介绍,你会发现里面提到了Trampoline的概念。 function() use($n, $accumulator) { return factorial($n - 1, $accumulator * $n); }; } function trampoline while (is_callable($result)) { $result = $result(); } return $result; } var_dump(trampoline 除非能提升代码可读性,否则没有必要使用递归;迫不得已之时,最好考虑使用Tail Call或Trampoline等技术来规避潜在的栈溢出问题。
sret的去向 scause:RISC-V在此处放置了一个数字来描述当前程序发生异常的原因等信息 sscratch:内核在这里放置了一个值,一般用于临时存储上一个程序的返回地址,方便程序最后跳转回原来程序 trampoline 将用户态原本要执行的下一条指令地址保存到sepc中,保证操作系统执行完内核态的代码后能够跳转回用户态继续执行 (3)跳转到stvec对应的地址执行内核处理程序 因此在执行完ecall后,当前便切换到了内核态中执行程序代码 Step2: Trampoline trampoline意为蹦床,字面理解就是来回蹦,实际上也确实是这样,当操作系统切换状态时,总会执行这段程序来完成状态的切换,这里我们可以从trampoline.S对应的源码来分析: 首先是这段代码: usertrapret()函数中,来完成跳转到用户态的操作 接下来在usertrapret()函数中,我们会将用户态对应的页表地址(satp)、栈针地址(sp)、编号(hartid)等信息恢复,之后通过计算trampoline 中的userret地址,再次返回到trampoline中,执行接下来的userret操作 在接下来trampoline的userret中,将原本的用户态首地址信息恢复,之后恢复用户态的寄存器信息,恢复用户态的返回
_; const void* quick_imt_conflict_trampoline_; const void* quick_generic_jni_trampoline_; // :Runtime 中获得了,因此可以用前面类似的搜索方法依次找出这几个 trampoline 的地址。 quick trampoline 一般是指被编译成 native 代码后的字节码在运行过程中所使用到的跳转地址表,比如 quick_resolution_trampoline_ 所指向的 stub 作用就是给 (native 代码)第一次调用某个方法时候解析指定方法,同理 generic_jni_trampoline 就是用来跳转到 JNI 方法(代码) 的 stub,quick_to_interpreter_bridge_trampoline 以 ARM64 为例,trampoline 的部分代码如下所示: Memory.patchCode(trampoline, 256, code => { const writer = new Arm64Writer
EmitOrAwait[Nothing, O] case class Await[+F[_], A, +O]( req: F[A] , rcv: (EarlyCause \/ A) => Trampoline [Process[F, O]] @uncheckedVariance , preempt : A => Trampoline[Process[F,Nothing]] @uncheckedVariance = (_:A) => Trampoline.delay(halt:Process[F,Nothing]) ) extends HaltEmitOrAwait[F, O] with EmitOrAwait 值得注意的是不但Await和Append这两个状态转换方式是结构化的,它们的连接函数(continuation)运算结果也是包嵌在Trampoline里的。 Used in conjunction with `Step`. */ case class Cont[+F[_], +O](stack: Vector[Cause => Trampoline
当然,我们前面讨论的Trampoline类型是最佳选择。 [A] { 14 def unit(a: A): Trampoline[A] = Done(a) 15 def flatMap[B](f: A => Trampoline[B]): Trampoline [A](k: () => Trampoline[A]) extends Trampoline[A] 它们有许多相似点。 我们可以把Trampoline类型的算法引进到IO类型中,这样就可以有效防止StackOverflow问题。 我们可以直接使用Free类型代表IO运算:用Free的Monadic编程语言来描述IO算法,用Interpreter来描述IO效果,用Free的Trampoline运算机制实现尾递归运算。
) 文档原文是Trampoline,可惜我没明白是什么意思。 尾递归需要调用闭包的trampoline()方法,它会返回一个TrampolineClosure,具有尾递归特性。 注意这里我们需要将外层闭包和递归闭包都调用trampoline()方法,才能正确的使用尾递归特性。然后我们计算一个很大的数字,就不会出现爆栈错误了。 def factorial factorial = { int n, def accu = 1G -> if (n < 2) return accu factorial.trampoline (n - 1, n * accu) } factorial = factorial.trampoline() assert factorial(1) == 1 assert factorial(
height:30px;background-color:#FF5722;border-radius:50%;box-shadow:0010pxrgba(0,0,0,0.3);z-index:5;}.trampoline divclass="score-container">分数:<spanid="score">0
object Future { case class Now[+A](a: A) extends Future[A] case class Async[+A](onFinish: (A => Trampoline : () => Future[A], f: A => Future[B]) extends Future[B] case class BindAsync[A,B](onFinish: (A => Trampoline fdelay,fapply分别把运算存入trampoline进行结构化了。 我们必须另外运算trampoline来运行结构内的运算: 1 fdelay.run //> run... 2 * exceptions. */ def async[A](listen: (A => Unit) => Unit): Future[A] = Async((cb: A => Trampoline
] >: F[x], O2 >: O](f: Cause => Process[F2, O2]): Process[F2, O2] = { val next = (t: Cause) => Trampoline.delay . */ final def asFinalizer: Process[F, O] = { def mkAwait[F[_], A, O](req: F[A], cln: A => Trampoline [Process[F,Nothing]])(rcv: EarlyCause \/ A => Trampoline[Process[F, O]]) = Await(req, rcv,cln) step } case Step(Await(req, rcv, cln), cont) => mkAwait(req, cln) { case -\/(Kill) => Trampoline.delay (Try(r.fold(Halt.apply, a => rcv(a) onComplete release(a) ))) }, { a: A => Trampoline.delay(release
xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0"; //mov rax, xxx ; jmp rax KIRQL Irql; ULONG SsdtEntry; PVOID trampoline ); // mov rax, @NewFunc; jmp rax *(PULONGLONG)(jmp_to_newFunction+2) = (ULONGLONG)hookedFunc; trampoline = SearchCodeCave(searchAddr); Dbg("trampoline : %llx\n", trampoline); mdl = IoAllocateMdl(trampoline NormalPagePriority); RtlMoveMemory(memAddr, jmp_to_newFunction, 12); SsdtEntry = GetSSDTEntry(KiServiceTable, trampoline