(转)调试器工作原理(1):基础篇

本文是一系列探究调试器工作原理的文章的第一篇。我还不确定这个系列需要包括多少篇文章以及它们所涵盖的主题,但我打算从基础知识开始说起。

关于本文

我打算在这篇文章中介绍关于Linux下的调试器实现的主要组成部分——ptrace系统调用。本文中出现的代码都在32位的Ubuntu系统上开发。请注意,这里出现的代码是同平台紧密相关的,但移植到别的平台上应该不会太难。

动机

要想理解我们究竟要做什么,试着想象一下调试器是如何工作的。调试器可以启动某些进程,然后对其进行调试,或者将自己本身关联到一个已存在的进程之上。它可以单步运行代码,设置断点然后运行程序,检查变量的值以及跟踪调用栈。许多调试器已经拥有了一些高级特性,比如执行表达式并在被调试进程的地址空间中调用函数,甚至可以直接修改进程的代码并观察修改后的程序行为。

尽管现代的调试器都是复杂的大型程序,但令人惊讶的是构建调试器的基础确是如此的简单。调试器只用到了几个由操作系统以及编译器/链接器提供的基础服务,剩下的仅仅就是简单的编程问题了。(可查阅维基百科中关于这个词条的解释,作者是在反讽)


Linux下的调试——ptrace

Linux下调试器拥有一个瑞士军刀般的工具,这就是ptrace系统调用。这是一个功能众多且相当复杂的工具,能允许一个进程控制另一个进程的运行,而且可以监视和渗入到进程内部。ptrace本身需要一本中等篇幅的书才能对其进行完整的解释,这就是为什么我只打算通过例子把重点放在它的实际用途上。让我们继续深入探寻。

 

遍历进程的代码

我现在要写一个在“跟踪”模式下运行的进程的例子,这里我们要单步遍历这个进程的代码——由CPU所执行的机器码(汇编指令)。我会在这里给出例子代码,解释每个部分,本文结尾处你可以通过链接下载一份完整的C程序文件,可以自行编译执行并研究。从高层设计来说,我们要写一个程序,它产生一个子进程用来执行一个用户指定的命令,而父进程跟踪这个子进程。首先,main函数是这样的:


int main(int argc, char** argv)
{
    pid_t child_pid;
 
    if (argc < 2) {
        fprintf(stderr, "Expected a program name as argument\n");
        return -1;
    }
 
    child_pid = fork();
    if (child_pid == 0)
        run_target(argv[1]);
    else if (child_pid > 0)
        run_debugger(child_pid);
    else {
        perror("fork");
        return -1;
    }
 
    return 0;
}

代码相当简单,我们通过fork产生一个新的子进程。随后的if语句块处理子进程(这里称为“目标进程”),而else if语句块处理父进程(这里称为“调试器”)。下面是目标进程:

void run_target(const char* programname)
{
    procmsg("target started. will run '%s'\n", programname);
 
    /* Allow tracing of this process */
    if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) {
        perror("ptrace");
        return;
    }
 
    /* Replace this process's image with the given program */
    execl(programname, programname, 0);
}


这部分最有意思的地方在ptrace调用。ptrace的原型是(在sys/ptrace.h):

long ptrace(enum __ptrace_request request,  pid_t pid, void *addr,  void *data);

第一个参数是request,可以是预定义的以PTRACE_打头的常量值。第二个参数指定了进程id,第三以及第四个参数是地址和指向数据的指针,用来对内存做操作。上面代码段中的ptrace调用使用了PTRACE_TRACEME请求,这表示这个子进程要求操作系统内核允许它的父进程对其跟踪。这个请求在man手册中解释的非常清楚:

“表明这个进程由它的父进程来跟踪。任何发给这个进程的信号(除了SIGKILL)将导致该进程停止运行,而它的父进程会通过wait()获得通知。另外,该进程之后所有对exec()的调用都将使操作系统产生一个SIGTRAP信号发送给它,这让父进程有机会在新程序开始执行之前获得对子进程的控制权。如果不希望由父进程来跟踪的话,那就不应该使用这个请求。(pid、addr、data被忽略)”

我已经把这个例子中我们感兴趣的地方高亮显示了。注意,run_target在ptrace调用之后紧接着做的是通过execl来调用我们指定的程序。这里就会像我们高亮显示的部分所解释的那样,操作系统内核会在子进程开始执行execl中指定的程序之前停止该进程,并发送一个信号给父进程。

因此,是时候看看父进程需要做些什么了:

void run_debugger(pid_t child_pid)
{
    int wait_status;
    unsigned icounter = 0;
    procmsg("debugger started\n");
 
    /* Wait for child to stop on its first instruction */
    wait(&wait_status);
 
    while (WIFSTOPPED(wait_status)) {
        icounter++;
        /* Make the child execute another instruction */
        if (ptrace(PTRACE_SINGLESTEP, child_pid, 0, 0) < 0) {
            perror("ptrace");
            return;
        }
 
        /* Wait for child to stop on its next instruction */
        wait(&wait_status);
    }
 
    procmsg("the child executed %u instructions\n", icounter);
}

通过上面的代码我们可以回顾一下,一旦子进程开始执行exec调用,它就会停止然后接收到一个SIGTRAP信号。父进程通过第一个wait调用正在等待这个事件发生。一旦子进程停止(如果子进程由于发送的信号而停止运行,WIFSTOPPED就返回true),父进程就去检查这个事件。

父进程接下来要做的是本文中最有意思的地方。父进程通过PTRACE_SINGLESTEP以及子进程的id号来调用ptrace。这么做是告诉操作系统——请重新启动子进程,但当子进程执行了下一条指令后再将其停止。然后父进程再次等待子进程的停止,整个循环继续得以执行。当从wait中得到的不是关于子进程停止的信号时,循环结束。在正常运行这个跟踪程序时,会得到子进程正常退出(WIFEXITED会返回true)的信号。

icounter会统计子进程执行的指令数量。因此我们这个简单的例子实际上还是做了点有用的事情——通过在命令行上指定一个程序名,我们的例子会执行这个指定的程序,然后统计出从开始到结束该程序执行过的CPU指令总数。让我们看看实际运行的情况。


实际测试

我编译了下面这个简单的程序,然后在我们的跟踪程序下执行:

#include <stdio.h>
int main()
{
    printf(“Hello, world!\n”);
    return 0;
}

令我惊讶的是,我们的跟踪程序运行了很长的时间然后报告显示一共有超过100000条指令得到了执行。仅仅只是一个简单的printf调用,为什么会这样?答案非常有意思。默认情况下,Linux中的gcc编译器会动态链接到C运行时库。这意味着任何程序在运行时首先要做的事情是加载动态库。这需要很多代码实现——记住,我们这个简单的跟踪程序会针对每一条被执行的指令计数,不仅仅是main函数,而是整个进程。

因此,当我采用-static标志静态链接这个测试程序时(注意到可执行文件因此增加了500KB的大小,因为它静态链接了C运行时库),我们的跟踪程序报告显示只有7000条左右的指令被执行了。这还是非常多,但如果你了解到libc的初始化工作仍然先于main的执行,而清理工作会在main之后执行,那么这就完全说得通了。而且,printf也是一个复杂的函数。

我们还是不满足于此,我希望能看到一些可检测的东西,例如我可以从整体上看到每一条需要被执行的指令是什么。这一点我们可以通过汇编代码来得到。因此我把这个“Hello,world”程序汇编(gcc -S)为如下的汇编码:

section    .text
    ; The _start symbol must be declared for the linker (ld)
    global _start
 
_start:
 
    ; Prepare arguments for the sys_write system call:
    ;   - eax: system call number (sys_write)
    ;   - ebx: file descriptor (stdout)
    ;   - ecx: pointer to string
    ;   - edx: string length
    mov    edx, len
    mov    ecx, msg
    mov    ebx, 1
    mov    eax, 4
 
    ; Execute the sys_write system call
    int    0x80
 
    ; Execute sys_exit
    mov    eax, 1
    int    0x80
 
section   .data
msg db    'Hello, world!', 0xa
len equ    $ - msg

这就足够了。现在跟踪程序会报告有7条指令得到了执行,我可以很容易地从汇编代码来验证这一点。


深入指令流

汇编码程序得以让我为大家介绍ptrace的另一个强大的功能——详细检查被跟踪进程的状态。下面是run_debugger函数的另一个版本:

void run_debugger(pid_t child_pid)
{
    int wait_status;
    unsigned icounter = 0;
    procmsg("debugger started\n");
 
    /* Wait for child to stop on its first instruction */
    wait(&wait_status);
 
    while (WIFSTOPPED(wait_status)) {
        icounter++;
        struct user_regs_struct regs;
        ptrace(PTRACE_GETREGS, child_pid, 0, ®s);
        unsigned instr = ptrace(PTRACE_PEEKTEXT, child_pid, regs.eip, 0);
 
        procmsg("icounter = %u.  EIP = 0x%08x.  instr = 0x%08x\n",
                    icounter, regs.eip, instr);
 
        /* Make the child execute another instruction */
        if (ptrace(PTRACE_SINGLESTEP, child_pid, 0, 0) < 0) {
            perror("ptrace");
            return;
        }
 
        /* Wait for child to stop on its next instruction */
        wait(&wait_status);
    }
 
    procmsg("the child executed %u instructions\n", icounter);
}

同前个版本相比,唯一的不同之处在于while循环的开始几行。这里有两个新的ptrace调用。第一个读取进程的寄存器值到一个结构体中。结构体user_regs_struct定义在sys/user.h中。这儿有个有趣的地方——如果你打开这个头文件看看,靠近文件顶端的地方有一条这样的注释:


/* 本文件的唯一目的是为GDB,且只为GDB所用。对于这个文件,不要看的太多。除了GDB以外不要用于任何其他目的,除非你知道你正在做什么。*/

现在,我不知道你是怎么想的,但我感觉我们正处于正确的跑道上。无论如何,回到我们的例子上来。一旦我们将所有的寄存器值获取到regs中,我们就可以通过PTRACE_PEEKTEXT标志以及将regs.eip(x86架构上的扩展指令指针)做参数传入ptrace来调用。我们所得到的就是指令。让我们在汇编代码上运行这个新版的跟踪程序。


$ simple_tracer traced_helloworld
[5700] debugger started
[5701] target started. will run 'traced_helloworld'
[5700] icounter = 1.  EIP = 0x08048080.  instr = 0x00000eba
[5700] icounter = 2.  EIP = 0x08048085.  instr = 0x0490a0b9
[5700] icounter = 3.  EIP = 0x0804808a.  instr = 0x000001bb
[5700] icounter = 4.  EIP = 0x0804808f.  instr = 0x000004b8
[5700] icounter = 5.  EIP = 0x08048094.  instr = 0x01b880cd
Hello, world!
[5700] icounter = 6.  EIP = 0x08048096.  instr = 0x000001b8
[5700] icounter = 7.  EIP = 0x0804809b.  instr = 0x000080cd
[5700] the child executed 7 instructions

OK,所以现在除了icounter以外,我们还能看到指令指针以及每一步的指令。如何验证这是否正确呢?可以通过在可执行文件上执行objdump –d来实现:

$ objdump -d traced_helloworld
 
traced_helloworld:     file format elf32-i386
 
Disassembly of section .text:
 
08048080 <.text>:
 8048080:     ba 0e 00 00 00          mov    $0xe,%edx
 8048085:     b9 a0 90 04 08          mov    $0x80490a0,%ecx
 804808a:     bb 01 00 00 00          mov    $0x1,%ebx
 804808f:     b8 04 00 00 00          mov    $0x4,%eax
 8048094:     cd 80                   int    $0x80
 8048096:     b8 01 00 00 00          mov    $0x1,%eax
 804809b:     cd 80                   int    $0x80

用这份输出对比我们的跟踪程序输出,应该很容易观察到相同的地方。

关联到运行中的进程上

你已经知道了调试器也可以关联到已经处于运行状态的进程上。看到这里,你应该不会感到惊讶,这也是通过ptrace来实现的。这需要通过PTRACE_ATTACH请求。这里我不会给出一段样例代码,因为通过我们已经看到的代码,这应该很容易实现。基于教学的目的,这里采用的方法更为便捷(因为我们可以在子进程刚启动时立刻将它停止)。

 

代码

本文给出的这个简单的跟踪程序的完整代码(更高级一点,可以将具体指令打印出来)可以在这里找到。程序通过-Wall –pedantic –std=c99编译选项在4.4版的gcc上编译。

 

结论及下一步要做的

诚然,本文并没有涵盖太多的内容——我们离一个真正可用的调试器还差的很远。但是,我希望这篇文章至少已经揭开了调试过程的神秘面纱。ptrace是一个拥有许多功能的系统调用,目前我们只展示了其中少数几种功能。

能够单步执行代码是很有用处的,但作用有限。以“Hello, world”为例,要到达main函数,需要先遍历好几千条初始化C运行时库的指令。这就不太方便了。我们所希望的理想方案是可以在main函数入口处设置一个断点,从断点处开始单步执行。下一篇文章中我将向您展示该如何实现断点机制。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《ace技术内幕:深入解析ace架构设计与实现原理》从构架模式、编程示例和源代码3个维度系统地对经典网络框架ace(adaptivemunicationenvironment)的架构设计和实现原理进行了深入分析,它能解决4个方面的问题:,帮助框架设计者领略软件框架设计的普适原则和思想,进而设计出自己的软件框架;第二,帮助ace应用开发人员加深对ace框架的理解,提升开发水平,更好地去定制和扩展ace框架,以及解决c 网络通信中的难题;第三,帮助c 开发人员加深c 语言功底,书中有大量对c 源代码的分析,包括网络编程、动态库编程和异步编程等,还涉及10余个经典的设计模式的解析;第四,增强平台开发人员和软件架构师的技术修养,ace的设计和实现都极其,它的实现源码和架构思想非常值得去学习和研究。 全书一共7章,详细分析了ace的reactor、serviceconfigurator、task、acceptor_connector、proactor和streams等6个框架的架构设计与实现原理。每个框架的分析分为3部分:,框架的设计分析,每个框架(除task框架)都有一个构架模式与之对应,构架模式阐述了框架的设计原理,给出了框架的总体结构,是学习框架的理论基础;第二,框架的应用分析,每个框架都有一个应用实例与之对应,应用实例既帮助读者了解框架的使用方法,又为读者提供了一个可以调试的应用程序,便于读者使用调试器探索框架的内部秘密;第三,框架的实现分析,这是本书的重点,对框架的实现原理进行了详细的分析,并且对重点的类和流程给出了uml类图和uml顺序图,可以让读者在短时间内掌握框架的实现技术。
第1章 FPGA/CPLD简介 1.1 可编程逻辑设计技术简介 1.1.1 可编程逻辑器件发展简史 1.1.2 可编程逻辑器件分类 1.2 FPGA/CPLD的基本结构 1.2.1 FPGA的基本结构 1.2.2 CPLD的基本结构 1.2.3 FPGA和CPLD的比较 1.3 FPGA/CPLD的设计流程 1.4 FPGA/CPLD的常用开发工具 1.5 下一代可编程逻辑设计技术展望 1.5.1 下一代可编程逻辑器件硬件上的四大发展趋势 1.5.2 下一代EDA软件设计方法发展趋势 1.6 小结 1.7 问题与思考 第2章 Altera FPGA/CPLD的结构 2.1 Altera高密度FPGA 2.1.1 主流高端FPGA——Stratix 2.1.2 内嵌高速串行收发器的FPGA Stratix GX 2.1.3 新一代90nm高端FPGA StratiX II 2.2 Altera低成本FPGA. 2.2.1 主流低成本FPGA Cyclone 2.2.2 新一代低成本FPGA Cyclone II 2.3 Altera的CPLD器件 2.3.1 主流的CPLD MAX 3000A 2.3.2 CPLD的革 MAX II 2.4 小结 2.5 问题与思考 第3章 Altera Quartus II开发流程 3.1 Quartus II软件综述 3.1.1 Quartus II软件的特点及支持的器件 3.1.2 Quartus II软件的工具及功能简介 3.1.3 Quartus II软件的用户界面 3.2 设计输入 3.2.1 设计输入方式 3.2.2 设计规划 3.2.3 设计输入文件实例 3.2.4 设计约束 3.3 综合 3.3.1 使用Quartus II软件集成综合 3.3.2 控制综合 3.3.3 综合实例 3.3.4 第三方综合工具 3.4 布局布线 3.4.1 设置布局布线参数 3.4.2 布局布线实例 3.4.3 增量布局布线 3.4.4 反标保留分配 3.5 仿真 3.5.1 指定仿真器设置 3.5.2 建立矢量源文件 3.5.3 仿真实例 3.5.4 第三方仿真工具 3.6 编程与配置 3.6.1 建立编程文件 3.6.2 器件编程和配置 3.7 小结 3.8 问题与思考 第4章 Altera的IP工具 4.1 IP的概念、Altera的IP 4.1.1 IP的概念 4.1.2 Altera可提供的IP 4.1.3 Altera IP在设计中的作用 4.2 使用Altera的基本宏功能 4.2.1 定制基本宏功能 4.2.2 实现基本宏功能 4.2.3 设计实例 4.3 使用Altera的IP核 4.3.1 定制IP核 4.3.2 实现IP核 4.3.3 设计实例 4.4 小结 4.5 问题与思考 第5章 Quartus II的常用辅助设计工具 5.1 I/O分配验证 5.1.1 I/O分配验证功能简介 5.1.2 I/O分配验证流程 5.1.3 用于I/O分配验证的输入 5.1.4 运行I/O分配验证 5.2 功率分析 5.2.1 Excel.based功率计算器 5.2.2 Simulation-based功率估算 5.3 RTL阅读器 5.3.1 RTL阅读器简介 5.3.2 RTL阅读器用户界面 5.3.3 原理图的分页和模块层次的切换 5.3.4 过滤原理图 5.3.5 将原理图中的节点定位到源设计文件 5.3.6 在原理图中查找节点或网线 5.3.7 使用RTL阅读器分析设计中的问题 5.4 SignalProbe及SignalTap II逻辑分析器 5.4.1 SignalProbe 5.4.2 SignalTap II逻辑分析器 5.5 时序收敛平面布局规划器(Timing Closure Floorplan) 5.5.1 使用Timing Closure Floorplan分析设计 5.5.2 使用Timing Closure Floorplan优化设计 5.6 Chip Editor底层编辑器 5.6.1 Chip Editor功能简介 5.6.2 使用Chip Editor的设计流程 5.6.3 Chip Editor视图 5.6.4 资源特性编辑器 5.6.5 Chip Editor的一般应用 5.7 工程更改管理(ECO) 5.7.1 ECO简介 5.7.2 ECO的应用范围 5.7.3 ECO的操作流程 5.7.4 使用Change Manager查看和管理更改 5.7.5 ECO验证 5.8 小结 5.9 问题与思考 第6章 编程与配置 6.1 配置Altera FPGA 6.1.1 配置方式 6.1.2 主动串行(AS) 6.1.3 被动串行(PS) 6.1.4
BPM4工作流应用开发指南 《jBPM4工作流应用开发指南》是2010年电子工业出版社出版的图书,作者是胡奇。 完全解析jBPM4应用开发技术 9位工作流业内专家联袂推荐 随着在现代企业管理中对于信息化、流程化的深度挖掘,具有业务流程管理的技术和思想已经成为致力于全面掌控企业级应用系统人士“日常生活、居家旅行”的必备素质。 本书分两。第一介绍工作流管理技术的概念、起源和发展历程,开源工作流选型,以及jBPM——这个迄今为止最成功的Java开源工作流项目的“前世今生”。此外,本还可以帮助读者快速上手jBPM4、使用jBPM4开发企业流程应用,包括安装和配置jBPM4、使用jBPM图形化流程设计器(GPD)设计流程、把流程部署到服务器上去、使用jBPM 4 Service API控制流程、掌握jBPM流程定义语言、流程变量、流程脚本。第二主要涉及基于jBPM4这个强大的应用程序框架打造属于自己独特业务的“企业流程管理平台”,包括jBPM4扩展研发先决条件、深入jPDL和jBPM Service API、升级jBPM3到jBPM4、流程虚拟机原理、jBPM4的设计思想、随需而配jBPM4、异步工作执行器、深入jBPM4电子邮件支持、系统日志、jBPM4与Spring框架集成、jBPM4与JBoss应用服务器集成、中国特色工作流的jBPM实现。 本书结构条理清晰,实践例程与理论思想紧密结合,翔实易懂,由浅入深,具有很强的参考性和实用性。本书适合所有掌握JavaEE(Java企业级版本)开发技术的人员——无论您是技术开发者、项目实施者、系统架构师,还是流程分析师、业务方案顾问,本书都适合您。 第一 jBPM工作流开发基础 1 第1章 工作流基础 2 1.1 工作流概念 2 1.1.1 工作流管理思想之于企业现代化管理 2 1.1.2 工作流技术在企业中的应用 5 1.1.3 如何从一个开发者的角度看工作流技术 6 1.2 工作流管理系统的发展历程 9 1.2.1 工作流管理系统参考模型 11 1.2.2 BPM 15 1.3 开源工作流选型 16 1.4 jBPM 19 1.4.1 jBPM前世今生 19 1.4.2 关于jBPM4您需要知道的 19 1.5 小结 23 第2章 安装和配置jBPM4 24 2.1 jBPM4安装先决条件 24 2.2 快速开始吧 26 2.3 安装脚本详解 27 2.3.1 关于配置文件 30 2.3.2 关于依赖库 31 2.4 安装到JBoss 31 2.5 安装到Tomcat 32 2.6 基于Web的Signavio流程设计器 33 2.6.1 jBPM Web流程设计器简介 33 2.6.2 独立安装Signavio 34 2.6.3 配置Signavio 34 2.7 用户自定义jBPM Web应用程序 35 2.8 安装jBPM数据库 35 2.8.1 新数据库安装 36 2.8.2 升级旧的数据库 36 2.9 安装图形化流程设计器(GPD) 37 2.9.1 获取Eclipse 37 2.9.2 在Eclipse中安装GPD插件 37 2.9.3 配置jBPM运行环境 38 2.9.4 添加jPDL4 Schema校验 41 2.9.5 导入和使用范例 41 2.10 例程:jBPM HelloWorld 43 2.11 小结 45 第3章 使用jBPM图形化流程设计器(GPD)设计流程 46 3.1 创建一个新流程 47 3.2 编辑流程定义源 49 3.3 例程:设计一个“复杂的”业务流程 49 3.4 小结 53 第4章 把流程部署到服务器上去 54 4.1 部署流程定义和资源文件 54 4.2 部署流程Java类的3个方法 57 4.3 例程:部署业务流程定义 58 4.4 小结 61 第5章 使用jBPM4 Service API控制流程 62 5.1 流程定义、流程实例和执行的概念 62 5.2 流程引擎API 64 5.3 利用API部署流程 67 5.4 通过API删除已部署的流程 69 5.5 使用API发起新的流程实例 69 5.5.1 发起流程实例的常规方法 70 5.5.2 指定业务键发起流程实例 70 5.5.3 指定变量发起流程实例 71 5.6 唤醒一个等待状态的执行 71 5.7 任务服务API 72 5.8 历史服务API 75 5.9 管理服务API 76 5.10 查询服务API 77 5.11 例程:利用jBPM Service API完成流程实例 78 5.12 小结 80 第6章 掌握jBPM流程定义语言 81 6.1 process(流程) 82 6.2 流控制活动 84 6.2.1 start(开始活动) 85 6.2.2 state(状态活动) 86 6.2.3 decision(判断活动) 89 6.2.4 fork – join(分支/聚合活动) 97 6.2.5 end(结束活动) 102 6.2.6 task(人工任务活动) 107 6.2.7 sub-process(子流程活动) 120 6.2.8 自定义活动 132 6.3 自动活动 134 6.3.1 java(Java程序活动) 135 6.3.2 script(脚本活动) 139 6.3.3 hql(Hibernate查询语言活动) 144 6.3.4 sql(结构化查询语言活动) 147 6.3.5 mail(邮件活动) 149 6.4 事件 153 6.4.1 事件监听 155 6.4.2 事件传播 157 6.4.3 处理异常事件 159 6.5 异步执行 160 6.5.1 异步活动 162 6.5.2 异步分支/聚合 164 6.6 用户代码 166 6.6.1 用户代码的定义 166 6.6.2 用户代码的类加载 168 6.7 小结 170 第7章 流程变量 171 7.1 变量作用域 173 7.2 变量类型 174 7.3 变量的自动更新和序列化 175 7.4 例程:用变量去控制一个流程的运行 177 7.5 小结 179 第8章 流程脚本 182 8.1 Java统一表达式语言 182 8.1.1 语法特点 183 8.1.2 值和方法表达式 184 8.1.3 隐式对象 187 8.1.4 运算符和保留字 188 8.1.5 一些经典EL表达式的例子 190 8.2 例程:用脚本去控制一个流程的运行 192 8.3 小结 194 第二 定制属于自己的流程——深入jBPM4扩展研发 195 第9章 jBPM4扩展研发先决条件 196 9.1 深入应用jBPM4所需要知道的 196 9.1.1 如果您的业务基于复杂的规则,在jBPM中加入Drools吧 196 9.1.2 抉择,是否使用BPEL 197 9.2 Maven仓库和Java依赖库 199 9.3 小结 200 第10章 深入jPDL和jBPM Service API 201 10.1 timer(定时器)能为您做什么 201 10.1.1 持续时间表达式 202 10.1.2 工作日历 202 10.1.3 定时移 204 10.1.4 定时事件 205 10.1.5 工作日历定时 207 10.1.6 定时重复 208 10.2 使用group活动编组流程 209 10.3 如何在活动中调用EJB方法 214 10.4 使用jms活动 215 10.4.1 模拟JMS服务 217 10.4.2 JMS文本消息 219 10.4.3 JMS Object消息 220 10.4.4 JMS Map消息 222 10.5 历史会话监听链 223 10.6 自定义Web任务表单 225 10.6.1 基本思路 225 10.6.2 表单格式 226 10.7 流程实例的自动迁移 228 10.7.1 简单的流程实例迁移 230 10.7.2 终止流程实例运行的迁移 232 10.7.3 应用活动映射的迁移 234 10.7.4 自定义迁移处理器 236 10.8 小结 239 第11章 升级jBPM3到jBPM4 240 11.1 你所要知道的升级局限性 241 11.2 流程定义换工具 242 11.2.1 命令行执行 242 11.2.2 Java编码执行 243 11.3 jBPM3到jBPM4的语义变更及翻译 244 11.4 小结 246 第12章 流程虚拟机原理 247 12.1 PVM的架构 247 12.2 PVM的实现 250 12.3 小结 253 第13章 jBPM4的设计思想 254 13.1 API设计 254 13.1.1 活动API 256 13.1.2 事件监听API 256 13.2 执行环境设计 257 13.3 命令设计 258 13.4 服务设计 259 13.5 历史流程处理原理 262 13.6 数据持久化设计 263 13.6.1 jBPM4流程定义资源和实例运行时数据表 264 13.6.2 jBPM4流程历史数据表 265 13.6.3 jBPM4身份认证数据表 266 13.6.4 jBPM4引擎属性数据表 267 13.7 例程:扩展jBPM4的API满足客户化的需求 268 13.8 小结 270 第14章 随需而配jBPM4 272 14.1 配置文件设计概要 273 14.2 配置工作日历 274 14.3 配置身份认证组件(组织适配器) 274 14.4 小结 277 第15章 异步工作执行器 278 15.1 设计原理 278 15.2 配置使用 280 15.3 小结 281 第16章 深入jBPM4电子邮件支持 282 16.1 电子邮件的产生 282 16.2 电子邮件服务器 285 16.3 电子邮件扩展 287 16.4 小结 289 第17章 系统日志 290 17.1 配置日志 290 17.2 日志输出级别 292 17.3 Java Logging API日志 292 17.4 利用持久化层日志进行调试 294 17.5 小结 295 第18章 jBPM4与Spring框架集成 296 18.1 集成的目标 297 18.2 为集成配置jBPM4 297 18.3 为集成配置Spring 299 18.4 使用 301 18.5 测试 302 18.6 小结 302 第19章 jBPM4与JBoss应用服务器集成 303 19.1 流程定义打包部署 303 19.2 在JBoss企业级编程模型中使用jBPM4 304 19.3 小结 306 第20章 中国特色工作流的jBPM实现 307 20.1 退回 308 20.2 取回 313 20.3 会签 318 20.4 委派 326 20.5 自由流 331 20.6 小结 332 附录A jBPM术语 334
物联网实验平台是大连飞翔科技有限公司自主研发的,定位于高校物联网专业应用和物联网实验室建设,该开发系统包括硬件设备、实验例程两大部分。硬件设备集成ARM9控制器网关、STM32W108无线节点、J-link在线调试烧写器及温度、湿度、光敏、声音、超声波、烟雾等传感器,可实现物联网网络构架。实验例程包括STM32W108的基础实验、传感器信息采集、无线信号收发实验、物联网网络构建及通讯实验、物联网网关软件交互控制实验等众多实验例程和典型应用。该实验平台实验分3个不同层次,从基础做起,层层提高,便于学生熟悉和掌握物联网技术的原理和实际应用,达到理论和动手能力的同步提升,将为教学和企业所需的专业人才培养构建一个良好的技术实验环境。 大连飞翔科技有限公司物联网教学实验平台-基础 实验1. 调试软件及J-link驱动的安装 实验2. STM32W108通用IO实验 实验3. 基于STM32W108中断实验 实验4. 无线传感器节点串口通信实验 实验5. 蜂鸣器实验 实验6. IIC通信实验 实验7. 温度传感器数据采集实验 实验8. 湿度传感器数据采集实验 实验9. 超声波传感器数据采集实验 实验10. 烟雾传感器数据采集实验 实验11. 声音传感器数据采集实验 实验12. 光敏传感器数据采集实验 实验13. LCD液晶屏显示实验 实验14. 基于STM32W uCOS移植实验
书名:PIC单片机实用教程.基础 出版社:北京航空航天大学出版社 作者:李学海 出版日期:2002-02-01 简介: 本书以介绍PIC16F877型号单片机为主,并适当兼顾PIC全系列,共分10章,内容包括:基本概念;PIC16F87X硬件概况;指令系统;汇编程序设计;集成开发环境;在线调试工具;I/O端口;定时器;中断;安全措施和降耗设计。突出特点:通俗易懂、可读性强、系统全面、学练结合、学用并重、实例丰富、习题齐全。 本书作为Microchip公司“大学计划”选择用书,可广泛适用于初步具备电子技术基础和计算机知识基础的学生、教师单片机爱好者、电子制作爱好者、电器维修人员、电子产品开发设计者、工程技术人员阅读。 本教程全书共分2,即基础和提高,分2册出版,以适应不同课时和不同专业的需要,也为教师和读者增加了一种可选方案。 目录: 第1章 单片机的基本概念 1.1 学习单片机有什么必要性 1.2 单片机究竟是什么 1.3 单片机有哪些应用 1.4 单片机有哪些特点 1.5 单片机的发展状况 1.6 PIC系列单片机有哪些优越之处 1.7 可在线调试和在线编程的PIC16F87X 第2章 PIC16F87X硬件系统概况 2.1 PIC16F87X封装形式和引脚功能 2.2 PIC16F87X内部结构方框图简介 2.3 程序存储器和堆栈 2.4 RAM数据存储器(文件寄存器) 2.5 复位功能简介 2.6 系统时钟简介 第3章 指令系统 3.1 指令时序 3.2 指令系统概览 3.3 面向字节操作类指令 3.4 面向位操作类指令 3.5 面向常数操作和控制操作类指令 3.6 寻址方式 3.7 数据传递关系 3.8 “内核-寄存器-外围模块”相互关系 第4章 PIC汇编语言程序设计基础 4.1 MPASM汇编器简介 4.2 汇编语言的语句格式 4.3 常用伪指令 4.4 程序格式和程序流程图 4.5 RAM数据存储器的体选寻址问题 4.6 顺序程序结构 4.7 分支程序结构 4.8 循环程序结构 4.9 子程序结构 4.10 程序跨页跳和跨页调用问题 4.11 延时程序设计 4.12 查表程序设计 第5章 MPLAB集成开发环境软件包 5.1 MPLAB的组成 5.2 MPLAB的安装 5.3 MPLAB的简单应用 第6章 MPLAB-ICD在线调试工具套件及其应用 6.1 概述 6.2 MPLAB- ICD工具套件的构成 6.3 MPLAB-ICD在线调试工具的安装 6.4 MPLAB-ICD在线调试工具的启用 6.5 用MPLAB-ICD统调用户程序和用户电路 第7章 输入/输出端口的基本功能 7.1 与输入/输出端口相关的寄存器 7.2 基本输入/输出端口的内部结构和工作原理 7.3 输入/输出瑞口基本功能的应用举例 第8章 定时器/计数器TMRO 8.1 定时器/计数器模块的基本用途 8.2 PIC系列单片机中定时器/计数器TMRO模块的特性 8.3 与定时器/计数器TMRO模块相关的寄存器 8.4 定时器/计数器TMRO模块的电路结构和工作原理 8.5 定时器/计数器TMRO模块的应用举例 第9章 中断系统 9.1 中断的基本概念 9.2 PIC16F87X的中断源 9.3 PIC16F87X的中断硬件逻辑 9.4 中断相关的寄存器 9.5 中断的处理 …… 第10章 安全生产、可靠运行措施和降耗设计
《Java Web开发教程: 入门与提高(JSP Servlet)PDF》是一本针对Java Web开发的教程,旨在帮助读者从入门到提高掌握JSP和Servlet的使用。 这本教程包括两个主要部分:JSP和Servlet。JSP(Java Server Pages)是一种动态网页开发技术,它允许开发人员在HTML网页上嵌入Java代码。Servlet是一种运行在服务器端的Java程序,用于处理客户端请求和生成响应。 在入门部分,教程首先介绍了Java Web开发的基础知识,包括HTML和CSS的基本语法、HTTP协议的工作原理等。然后,它详细介绍了JSP的语法和标签,包括如何在JSP页面中嵌入Java代码,如何使用JSTL(JSP标准标签库)等。同时,它还介绍了如何使用Eclipse等常用的开发工具来编写和调试JSP页面。 在提高部分,教程进一步深入讲解了Servlet的使用。它介绍了Servlet的生命周期、请求处理过程和Servlet API的常用类和方法。此外,教程还介绍了如何使用Cookie和Session来实现用户认证和数据共享,以及如何使用过滤器和监听器来实现请求过滤和监听事件。 该教程的特点是通俗易懂,结合实例和练习,帮助读者进行实践。此外,教程还提供了一些实际项目案例,帮助读者理解和应用所学知识。 总之,《Java Web开发教程: 入门与提高(JSP Servlet)PDF》是一本适合Java Web开发初学者和有一定基础的开发人员学习的教材,通过学习该教程,读者可以全面掌握JSP和Servlet的使用,为自己的Web开发之路做好铺垫。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值