我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Python反编译之字节码

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Python反编译之字节码

如果你曾经写过或者用过 Python,你可能已经习惯了看到 Python 源代码文件;它们的名称以.Py 结尾。你可能还见过另一种类型的文件是 .pyc 结尾的,它们就是 Python “字节码”文件。(在 Python3 的时候这个 .pyc 后缀的文件不太好找了,它在一个名为__pycache__的子目录下面。).pyc文件可以防止Python每次运行时都重新解析源代码,该文件大大节省了时间。

1240

Python是如何工作的

Python 通常被描述为一种解释语言,在这种语言中,你的源代码在程序运行时被翻译成CPU指令,但这只是说对了部分。和许多解释型语言一样,Python 实际上将源代码编译为虚拟机的一组指令,Python 解释器就是该虚拟机的实现。其中这种中间格式称为“字节码”。

因此,Python留下的这些.pyc文件,是为了让运行的速快变得 “更快”,或者是针对你的源代码的”优化“的版本;它们是 Python 虚拟机上运行的字节码指令。

Python 虚拟机内幕

CPython使用基于堆栈的虚拟机。也就是说,它完全围绕堆栈数据结构(你可以将项目“推”到结构的“顶部”,或者将项目“弹出”到“顶部”)。

CPython 使用三种类型的栈:

1.调用堆栈。这是运行中的Python程序的主要结构。对于每个当前活动的函数调用,它都有一个项目一“帧”,堆栈的底部是程序的入口点。每次函数调用都会将新的帧推到调用堆栈上,每次函数调用返回时,它的帧都会弹出。

2.在每一帧中,都有一个评估堆栈(也称为数据堆栈)。这个堆栈是执行 Python 函数的地方,执行Python代码主要包括将东西推到这个堆栈上,操纵它们,然后将它们弹出。

3.同样在每一帧中,都有一个块堆栈。Python使用它来跟踪某些类型的控制结构:循环、try /except块,以及 with 块都会导致条目被推送到块堆栈上,每当退出这些结构之一时,块堆栈就会弹出。这有助于Python知道在任何给定时刻哪些块是活动的,例如,continue或break语句可以影响正确的块。

大多数 Python 字节码指令操作的是当前调用栈帧的计算栈,虽然,还有一些指令可以做其它的事情(比如跳转到指定指令,或者操作块栈)。

为了更好地理解,假设我们有一些调用函数的代码,比如这个:

my_function(my_variable,2)。

Python 将转换为一系列字节码指令:

一个LOAD_NAME指令,用于查找函数对象 my_function,并将其推送到计算栈的顶部。

另一个 LOAD_NAME 指令去查找变量 my_variable,并将其推送到计算栈的顶部。

一个 LOAD_CONST 指令将一个整数 2 推送到计算栈的顶部。

一个 CALL_FUNCTION 指令。

CALL_FUNCTION 指令有2个参数,它表示 Python 需要在堆栈顶部弹出两个位置参数; 然后函数将在它上面进行调用,并且它也同时被弹出(关键字参数的函数,使用指令-CALL_FUNCTION_KW-类似的操作,并配合使用第三条指令CALL_FUNCTION_EX,它适用于函数调用涉及到参数使用 * 或 ** 操作符的情况)

一旦 Python 具备了这些,它将在调用堆栈上分配一个新的帧,填充到函数调用的本地变量,然后运行该帧内的 my_function 的字节码。一旦运行完成,帧将从调用堆栈中弹出,在原始帧中,my_function 的返回值将被推入到计算栈的顶部。

我们知道了这个东西了,也知道字节码了文件了,但是如何去使用字节码呢?ok不知道也没关系,接下来的时间我们所有的话题都将围绕字节码,在python有一个模块可以通过反编译Python代码来生成字节码这个模块就是今天要说的--dis模块。

dis模块的使用

dis模块包括一些用于处理 Python 字节码的函数,可以将字节码“反汇编”为更便于人阅读的形式。查看解释器运行的字节码还有助于优化代码。这个模块对于查找多线程中的竞态条件也很有用,因为可以用它评估代码中哪一点线程控制可能切换。参考源码Include/opcode.h,可以找到字节码的正式列表。详细可以看官方文档。注意不同版本的python生成的字节码内容可能不一样,这里我用的Python 3.8.

访问和理解字节码

输入如下内容,然后运行它:


1240

函数 dis.dis() 将反汇编一个函数、方法、类、模块、编译过的 Python 代码对象、或者字符串包含的源代码,以及显示出一个人类可读的版本。dis 模块中另一个方便的功能是 distb()。你可以给它传递一个 Python 追溯对象,或者在发生预期外情况时调用它,然后它将在发生预期外情况时反汇编调用栈上最顶端的函数,并显示它的字节码,以及插入一个指向到引发意外情况的指令的指针。

它也可以用于查看 Python 为每个函数构建的编译后的代码对象,因为运行一个函数将会用到这些代码对象的属性。这里有一个查看 hello() 函数的示例:


1240

代码对象在函数中可以以属性 __code__ 来访问,并且携带了一些重要的属性:

co_consts 是存在于函数体内的任意实数的元组

co_varnames 是函数体内使用的包含任意本地变量名字的元组

co_names 是在函数体内引用的任意非本地名字的元组

许多字节码指令--尤其是那些推入到栈中的加载值,或者在变量和属性中的存储值--在这些元组中的索引作为它们参数。

因此,现在我们能够理解 hello() 函数中所列出的字节码:

LOAD_GLOBAL 0:告诉 Python 通过 co_names (它是 print 函数)的索引 0 上的名字去查找它指向的全局对象,然后将它推入到计算栈。

LOAD_CONST 1:带入 co_consts 在索引 1 上的字面值,并将它推入(索引 0 上的字面值是 None,它表示在 co_consts 中,因为 Python 函数调用有一个隐式的返回值 None,如果没有显式的返回表达式,就返回这个隐式的值 )。

CALL_FUNCTION 1:告诉 Python 去调用一个函数;它需要从栈中弹出一个位置参数,然后,新的栈顶将被函数调用。

“原始的” 字节码--是非人类可读格式的字节--也可以在代码对象上作为 co_code 属性可用。如果你有兴趣尝试手工反汇编一个函数时,你可以从它们的十进制字节值中,使用列出 dis.opname 的方式去查看字节码指令的名字。

基本反汇编

函数dis()可以打印 Python 源代码(模块、类、方法、函数或代码对象)的反汇编表示。可以通过从命令行运行 dis 来反汇编 dis_simple.py 之类的模块。


1240

输出按列组织,包含原始源代码行号,代码对象中的指令地址,操作码名称以及传递给操作码的任何参数。

对于简单的代码我们可以通过命令行的形式执行下面的命令:

python3-mdisdis_simple.py

输出


1240

在这里源代码转换为4个不同的操作来创建和填充字典,然后将结果保存到一个局部变量。

首先解释每一行各列参数的含义:

以第一条指令为例:

第一列 数字(1)表示对应源代码的行数。

第二列(可选)指示当前执行的指令(例如,当字节码来自帧对象时)【这个例子没有】

第三列 一个标签,表示从之前的指令到此可能的JUMP 【这个例子没有】

第四列 数字是字节码中对应于字节索引的地址(这些是2的倍数,因为Python 3.6每条指令使用2个字节,而在以前的版本中可能会有所不同)指令LOAD_CONST在0位置。

第五列 指令本身对应的人类可读的名字这里是"LOAD_CONST"

第六列 Python内部用于获取某些常量或变量,管理堆栈,跳转到特定指令等的指令的参数(如果有的话)。

第七列 计算后的实际参数。

然后让我们看看这个过程:

由于 Python 解释器是基于栈的,所以前几步是用LOAD_CONST将常量按正确顺序放入到栈中,然后使用 BUILD_MAP 弹出要增加到字典的新键和值。用 STORE_NAME 将所得到的dict对象绑定名为my_dict.

反汇编函数

需要注意的是上面的命令行反编译的形式,不能自动的递归反编译函数,所以我们要使用在文件中导入dis的模式进行反编译,就像下面这样。


1240


运行命令

python3dis_function.py

然后得到以下结果


1240

要查看函数的内部,必须把函数传递到dis().因为这里打印的是函数内部的东西,所以没有显示函数的在外层的行编号,而是从2开始的。

下面解析下每一行指令的含义:

LOAD_GLOBAL 用来加载全局变量,包括指定函数名,类名,模块名等全局符号,这里是len函数,LOAD_FAST 一般加载局部变量的值,也就是读取值,用于计算或者函数调用传参等,这里就是传入参数args。

一般是先指定要调用的函数,然后压参数,最后通过 CALL_FUNCTION 调用。

STORE_FAST 保存值到局部变量。也就是把结果赋值给 STORE_FAST。

下面的print因为2个参数所以LOAD_FAST了2次,POP_TOP删除堆栈顶部(TOS)项。LOAD_CONST加载const变量,比如数值、字符串等等,这里因为是print所以值为None。

最后通过RETURN_VALUE来确定函数结尾。

要打印一个函数的总结信息我们可以使用dis的show_code的方法,它包含使用的参数和名的相关信息,show_code的参数就是这个函数对象,代码如下:


1240

运行之后,结果如下


1240


可以看到返回的内容有函数,方法,参数等信息。

反汇编类

上面我们知道了如何反汇编一个函数的内部,同样的我们也可以用类似的方法反汇编一个类。

我们看一个例子:


1240

运行之和得到如下结果


1240

从整体内容来看,结果分为了两部分Disassembly of __init__和Disassembly of __str__,Disassembly就是反汇编的意思。

首先分析__init__部分:

然后需要注意的一点是,方法是按照字母的顺序列出的,所以在部分,先看到name再看到self,但是他们都是 LOAD_FAST。

STORE_ATTR实现self.name = name。

然后LOAD_CONST一个None和RETURN_VALUE标志着函数结束。

接下来分析__str__部分:

LOAD_CONST将'MyObject({})'加载到栈

然后通过 LOAD_METHOD 调用字符串format方法。这个方法是Python3.7新加入的。

LOAD_FAST 也就是到了self了。

LOAD_ATTR 一般是调用某个对象的方法时。这里就是self.name的.name操作

CALL_METHOD 是 python3.7 新增加的内容,这里是执行方法。

RETURN_VALUE表示函数的结束。

上面字符串的拼接我们用了format,之前我一直推荐用f-string,下面就让我们通过字节码来分析,为什么f-string比format要高快。

代码其他代码不变,把return改成以下内容:

returnf'MyObject({self.name})'

再次执行,下面我们只看__str__函数的部分。


1240

对比发现我们这里没有了调用方法的操作LOAD_METHOD,取而代之使用了用于实现fstring的FORMAT_VALUE指令。之后通过BUILD_STRING连接堆栈中的计数字符串并将结果字符串推入堆栈.为什么format慢呢, python中的函数调用具有相当大的开销。 当使用str.format()时,CALL_METHOD 中花费的额外时间是导致str.format()比fstring慢得多。

使用反汇编调试

调试一个异常时,有时要查看哪个字节码带来了问题。这个时候就很有用了,要对一个错误周围的代码反汇编,有多种方法。第一种策略是在交互解释器中使用dis()报告最后一个异常。

如果没有向dis()传入任何参数,那么它会查找一个异常,并显示导致这个异常的栈顶元素的反汇编效果。

命令行上使用

打开我的命令行执行如下操作:


1240


行号后面的-->就是导致错误的操作码,一个LOAD_NAME指令,由于没有定义变量i,所以无法将与这个名关联的值加载到栈中。

代码中使用distb

程序还可以打印一个活动的traceback的有关信息,将它传递到distb()方法。

下面的程序中有个DiviedByZero异常;但是这个公式有两个除法,所以不清楚是哪一部分出错,此时我们就可以使用下面的方法:


1240


运行之后输出


1240

结果反映的字节码很长我们不用全看了,看最开始出现--> 就可以知道错误的位置了。

其中SETUP_FINALLY 字节码的含义是将try块从try-except子句推入块堆栈。

这里可以看出将LOAD_NAME 将j压入栈之后就报错了。所以可以推断出在(i/j)就出错了。

参考资料

https://docs.python.org/zh-cn/3.7/library/dis.html#opcode-STORE_FAST

https://opensource.com/article/18/4/introduction-python-bytecode

https://hackernoon.com/a-closer-look-at-how-python-f-strings-work-f197736b3bdb


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Python反编译之字节码

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Python反编译之字节码

如果你曾经写过或者用过 Python,你可能已经习惯了看到 Python 源代码文件;它们的名称以.Py 结尾。你可能还见过另一种类型的文件是 .pyc 结尾的,它们就是 Python “字节码”文件。(在 Python3 的时候这个 .p
2023-01-31

关于Java反编译字节码文件

将高级语言翻译成汇编语言或机器语言的过程Java语言中的编译一般指将Java文件转换成class文件顾名思义反编译就是编译的逆向过程其实我们常用的开发工具(例如:IDEA、Eclipse)都带有反编译功能,需要的朋友可以参考下
2023-05-19

如何使用java反编译字节码文件

本篇文章给大家分享的是有关如何使用java反编译字节码文件,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Java是什么Java是一门面向对象编程语言,可以编写桌面应用程序、We
2023-06-14

Python使用dis模块把Python反编译为字节码的用法详解

dis — Disassembler for Python bytecode,即把python代码反汇编为字节码指令. 使用超级简单:python -m dis xxx.pyPython 代码是先被编译为字节码后,再由Python虚拟机来执
2022-06-04

Android反编译代码和防止反编译

一、反编译apk文件 安装ApkTool工具,该工具可以解码得到资源文件,但不能得到Java源文件。 安装环境:需要安装JRE1.6 1> 到http://code.google.com/p/android-apktool
2022-06-06

如何将 Groovy 脚本直接编译成 Java 字节码?(Groovy脚本怎么直接编译成Java字节码)

在Java开发领域中,Groovy是一种非常强大的脚本语言,它可以与Java无缝集成,并且能够在Java虚拟机(JVM)上运行。Groovy脚本的开发效率非常高,同时又保留了Java的强大功能。然而,有时候我们需要将Groovy脚本直接编译成Java字节码,以便在一
如何将 Groovy 脚本直接编译成 Java 字节码?(Groovy脚本怎么直接编译成Java字节码)
groovyJava2024-12-13

Python中怎么实现编译和反编译

这篇文章将为大家详细讲解有关Python中怎么实现编译和反编译,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。一、用Pyinstaller打包python代码1. 安装Pyinstaller安
2023-06-16

Python底层技术揭秘:如何实现字节码编译器

Python底层技术揭秘:如何实现字节码编译器Python作为一门高级语言,其强大的特性和灵活性吸引着众多开发者。然而,要真正深入了解Python,我们需要深入其底层技术,探索其内部的工作机制。本文将带你揭秘Python底层的字节码编译器,
Python底层技术揭秘:如何实现字节码编译器
2023-11-08

对Python源码加密及反编译前后对比

目前软件开发商对 Python 加密时可能会有两种形式,一种是对python转成的exe进行保护,另一种是直接对.py或者.pyc文件进行保护,下面将列举两种形式的保护流程。1、 对 python转exe加壳下载最新版加壳工具,使用加壳工具
2023-01-31

使用java怎么获取字节码的编译版本

使用java怎么获取字节码的编译版本?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。示例代码package com.jinggujin.util;import java.io.D
2023-05-31

golang 编译之后的可执行文件能被反编译吗?

是的,go 编译后的可执行文件可以被反编译。反编译工具会分析其内部结构,提取代码信息,并转换为 go 代码。可用工具包括 gopherlua、godefy 和 golang-obf。不过,反编译可能会丢失部分源代码信息,混淆或加密的可执行文
golang 编译之后的可执行文件能被反编译吗?
2024-04-21

字节跳动合并编译实践

本文整理自字节跳动基础架构/服务框架团队尹旭然在 QCon 2024 的分享,主要介绍了服务框架团队编译期合并服务的技术实践和经验总结。

深入了解Golang编译过程与字节码的关联

探索Golang的编译过程与字节码关系Golang,是一个开源的静态类型编程语言,由Google开发。它以其高效的并发特性和简洁的语法而闻名,被广泛应用于各种领域的软件开发中。在Golang中,源代码编译后会生成与机器无关的中间代码,也就
深入了解Golang编译过程与字节码的关联
2024-02-26

python基础之字符编码

一 计算机基础知识 二 文本编辑器存取文件的原理(nodepad++,pycharm,word)#1、打开编辑器就打开了启动了一个进程,是在内存中的,所以,用编辑器编写的内容也都是存放与内存中的,断电后数据丢失#2、要想永久保存,需要点击保
2023-01-30

Python文件反编译怎么实现

本篇内容介绍了“Python文件反编译怎么实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!python的便利性,使得如今许多软件开发者、黑
2023-06-21

Android Apk反编译得到Java源代码

大家做Android开发,看到别人应用里一些好的功能,是不是很想得到源码,借鉴一下?既然Android是用JAVA开发的,那么我们能很容易的通过反编译的到应用的源代码。下面我简单介绍下应该怎么操作。具体步骤:1.首先将apk文件后缀改为RA
2022-06-06

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录