printf内幕----编程内幕(1)

    曾几何时,您有没有在夜深人静的时候想过一个问题,printf内部究竟做了什么?为何可以输出到屏幕上显示出来?

    先看看这段熟悉的代码:

   

//
//  Created by xi.chen on 2017/9/2.
//  Copyright © 2017 All rights reserved.
//#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>int main()
{printf("hello, my cat!\n");return 0;
}

环境:

Mac OSX 10.12.3

Apple LLVM version 8.1.0 (clang-802.0.42)

Target: x86_64-apple-darwin16.4.0

Xcode 8.3.3

首先,我们先看看汇编代码.

(__TEXT,__text) section
_main:
0000000100000f50	pushq	%rbp
0000000100000f51	movq	%rsp, %rbp
0000000100000f54	subq	$0x10, %rsp
0000000100000f58	leaq	0x3b(%rip), %rdi ## literal pool for: "hello, my cat!\n"
0000000100000f5f	movl	$0x0, -0x4(%rbp)
0000000100000f66	movb	$0x0, %al
0000000100000f68	callq	0x100000f7a ## symbol stub for: _printf
0000000100000f6d	xorl	%ecx, %ecx
0000000100000f6f	movl	%eax, -0x8(%rbp)
0000000100000f72	movl	%ecx, %eax
0000000100000f74	addq	$0x10, %rsp
0000000100000f78	popq	%rbp
0000000100000f79	retq

可以看到,核心就是

callq 0x100000f7a ## symbol stub for: _printf

0000000100000f68
f68是在可执行文件的偏移.
可以用MachOView查看可执行文件的内部结构.

0000f60 45 fc 00 00 00 00 b0 00 e8 0d 00 00 00 31 c9 89

指令e8 0d 00 00 00 是call指令,相当于调用子程序,跳转到当前指令后一条指令的PC值(0x100000f6d) + 偏移值(0d), 即0x100000f7a.
也就是上面callq之后的地址值.


上面没有分析,0x100000000是什么?

xichen:hello xichen$ xcrun size -x -l -m !$
xcrun size -x -l -m /Users/xichen/Library/Developer/Xcode/DerivedData/hello-bkhrmjvnrfikgkfgkiemoyorgkig/Build/Products/Debug/hello
Segment __PAGEZERO: 0x100000000 (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x1000 (vmaddr 0x100000000 fileoff 0)Section __text: 0x2a (addr 0x100000f50 offset 3920)Section __stubs: 0x6 (addr 0x100000f7a offset 3962)Section __stub_helper: 0x1a (addr 0x100000f80 offset 3968)Section __cstring: 0x10 (addr 0x100000f9a offset 3994)Section __unwind_info: 0x48 (addr 0x100000fac offset 4012)total 0xa2
Segment __DATA: 0x1000 (vmaddr 0x100001000 fileoff 4096)Section __nl_symbol_ptr: 0x10 (addr 0x100001000 offset 4096)Section __la_symbol_ptr: 0x8 (addr 0x100001010 offset 4112)total 0x18
Segment __LINKEDIT: 0x3000 (vmaddr 0x100002000 fileoff 8192)
total 0x100005000

这个数值可以看成加载器把可执行文件加载到内存的虚拟地址基址. (上面的所有指令都是基于这个地址为基址)
回到callq这条指令,会跳转到地址0x100000f7a,对应的指令是:

0000f70 45 f8 89 c8 48 83 c4 10 5d c3 ff 25 90 00 00 00

ff指令是jmp指令, 反汇编如下:

(lldb) x/3i 0x100000f7a0x100000f7a: ff 25 90 00 00 00     jmpq   *0x90(%rip)               ; (void *)0x0000000100000f900x100000f80: 4c 8d 1d 81 00 00 00  leaq   0x81(%rip), %r11          ; (void *)0x0000000000000000

jmpq跳转到: 0xf80 + 0x90地址的数值为地址的地方.

>>> hex(0xf80+0x90)
'0x1010'

即跳转到0x100001010.

(lldb) x/3g 0x100001010
0x100001010: 0x00007fffa8778180 0x0000000000000000
0x100001020: 0x0000000000000000

我们来看看printf的地址在哪里:

(lldb) dis -s printf
libsystem_c.dylib`printf:0x7fffa8778180 <+0>:  pushq  %rbp0x7fffa8778181 <+1>:  movq   %rsp, %rbp0x7fffa8778184 <+4>:  pushq  %r150x7fffa8778186 <+6>:  pushq  %r140x7fffa8778188 <+8>:  pushq  %rbx0x7fffa8778189 <+9>:  subq   $0xd8, %rsp0x7fffa8778190 <+16>: movq   %rdi, %r140x7fffa8778193 <+19>: testb  %al, %al0x7fffa8778195 <+21>: je     0x7fffa87781c3            ; <+67>0x7fffa8778197 <+23>: movaps %xmm0, -0xc0(%rbp)


0x7fffa8778180是不是和上面对上来了?
我们继续dump printf后面调用了什么:

(lldb)  x/50i 0x7fffa8778180
...............0x7fffa8778235: 48 0f 45 f0           cmovneq %rax, %rsi0x7fffa8778239: 48 8d 4d c0           leaq   -0x40(%rbp), %rcx0x7fffa877823d: 48 89 df              movq   %rbx, %rdi0x7fffa8778240: 4c 89 f2              movq   %r14, %rdx0x7fffa8778243: e8 c0 20 00 00        callq  0x7fffa877a308            ; vfprintf_l0x7fffa8778248: 4c 3b 7d e0           cmpq   -0x20(%rbp), %r150x7fffa877824c: 75 0e                 jne 

我们有幸可以看到mac开放的libc源代码:

int
printf(char const * __restrict fmt, ...)
{int ret;va_list ap;va_start(ap, fmt);ret = vfprintf_l(stdout, __current_locale(), fmt, ap);va_end(ap);return (ret);
}

调用vfprintf_l, 是不是感觉一切都在预期之内呢?
继续在libc跟踪一番,会发现最终会调用write系统调用完成.
write系统调用会使用int指令陷入内核,执行写数据的操作.

到此,您会不会有疑问,为何调用printf函数中跳转了好多次,是因为编译系统傻吗?当然不是,因为采用的是动态链接库, 主程序一开始并不知道调用的printf函数最终会在哪个地址,所以先保留了一个stub,等加载器加载运行时,再填入对应的地址.
就是上面的jmp跳转所实现的, 而call printf这个语句并不需要等运行时再计算地址,编译期就可以用此时设定的固定地址.

至此,我们已经理清了上面的flow. 那又是如何显示在屏幕上的呢?
如果从终端terminal开始,调用了上面的应用程序(比如hello),  terminal会fork一个进程, 并执行hello,然后等待hello完成 (此种是不带后台运行的模式).
hello调用了printf输出,printf是向stdout输出,为何向stdout会在此terminal上显示呢?
首先我们要明白,stdout究竟指向哪个设备?

xichen:hello xichen$ tty
/dev/ttys001

所以,printf其实是向/dev/ttys001设备去写. 
对应kernel的代码:

/** ttwrite (LDISC)** Process a write call on a tty device.** Locks:	Assumes tty_lock() is held prior to calling.*/
int
ttwrite(struct tty *tp, struct uio *uio, int flag)

终端会在tty有数据的时候,把数据画到屏幕上. (注意: printf后面的字符串是终端进程画到屏幕上的,不是hello画的,因为hello只是写文件,写文件当然不一定会显示到屏幕, 只是一般脑袋瓜子正常的终端都会回显对应的文本信息).

至于,如何把一段文本画到屏幕上,这个就不用多说了.

 


微风不燥,阳光正好,你就像风一样经过这里,愿你停留的片刻温暖舒心。

我是程序员小迷(致力于C、C++、Java、Kotlin、Android、Shell、JavaScript、TypeScript、Python等编程技术的技巧经验分享),若作品对您有帮助,请关注、分享、点赞、收藏、在看、喜欢,您的支持是我们为您提供帮助的最大动力。

欢迎关注。助您在编程路上越走越好!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/37295.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WordPress中文网址导航栏主题风格模版HaoWa

模板介绍 WordPress响应式网站中文网址导航栏主题风格模版HaoWa1.3.1源码 HaoWA主题风格除行为主体导航栏目录外&#xff0c;对主题风格需要的小控制模块都开展了敞开式的HTML在线编辑器方式的作用配备&#xff0c;另外预埋出默认设置的编码构造&#xff0c;便捷大伙儿在目前…

入门JavaWeb之 JDBC 连接数据库

JDBC&#xff1a;Java Database Connectivity&#xff0c;Java 数据库连接 需要 jar 包支持&#xff1a; java.sql javax.sql mysql-connector-java&#xff08;连接驱动&#xff0c;必须导入&#xff09; 在 MySQL 先建个 jdbc 数据库后 USE jdbc; 执行后再 CREATE TABLE…

15- 22题聚合函数 - 高频 SQL 50 题基础版

目录 1. 相关知识点2. 例子2.15 - 有趣的电影2.16 - 平均售价2.17 - 项目员工 I2.18 - 各赛事的用户注册率2.19 - 查询结果的质量和占比2.20 - 每月交易 I2.21 - 即时食物配送 II2.22 - 游戏玩法分析 IV 1. 相关知识点 函数 函数含义order by排序group by分组between 小值 an…

Chrome备份数据

Chrome备份数据 1、 导出谷歌浏览器里的历史记录 参考&#xff1a;https://blog.csdn.net/qq_32824605/article/details/127504219 在资源管理器中找到History文件&#xff0c;文件路径&#xff1a; C:\Users\你的电脑用户名\AppData\Local\Google\Chrome\User Data\Default …

堆排序思想分享

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

丢失的数字(MissNumber)

丢失的数字 给定一个包含 [0, n] 中 n 个数的数组 nums &#xff0c;找出 [0, n] 这个范围内没有出现在数组中的那个数。 示例 1&#xff1a; 输入&#xff1a;nums [3,0,1] 输出&#xff1a;2 解释&#xff1a;n 3&#xff0c;因为有 3 个数字&#xff0c;所以所有的数字都…

五、Pentium 微处理器保护模式存储管理,《微机系统》第一版,赵宏伟

一、分段存储管理 Pentium支持分段存储管理、分页存储管理和段页式存储管理。 1.1 分段存储管理的基本思想 一个程序由多个模块组成。 每一个模块都是一个特定功能的独立的程序段。 段式管理&#xff1a;把主存按段分配的存储管理方式。 程序模块→段→段描述符→段描述符…

【设计】在Java后端开发时使用JSONObject完全替代JAVABean(DTO,VO)是否可行?

其实这样做你是得不偿失&#xff0c;不过也要看什么项目&#xff0c;如果你的项目只在只需要实现功能&#xff0c;不在乎健壮性&#xff0c;可持续性那就完全可以。因为我现在公司老项目所有用的POJO的地方都是用JSONObject。代码可读性几乎为0。你用了可能丧失以下功能&#x…

【微服务】后台管理项目多数据源管理方案实战

目录 前言 1、使用Spring提供的AbstractRoutingDataSource 2、使用MyBatis注册多个SqlSessionFactory 3、使用dynamic-datasource框架 前言 Java后台使用MyBatis-plus 快速访问多个数 据源&#xff0c;这里分享三种常用的多数据源管理方案 1、使用Spring提供的AbstractRout…

【C++深度探索】继承机制详解(一)

hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#xff1a;大耳朵土土垚的博客 &#x1…

代码托管服务:GitHub、GitLab、Gitee

目录 引言GitHub&#xff1a;全球最大的代码托管平台概述功能特点适用场景 GitLab&#xff1a;一体化的开发平台概述功能特点适用场景 Gitee&#xff08;码云&#xff09;&#xff1a;中国本土化的代码托管服务概述功能特点适用场景 功能对比结论 引言 在现代软件开发中&#…

numpy - array(3)

arr1 np.array([[(1000, 1001, 1002, 1003), (1010, 1011, 1012, 1013), (1020, 1021, 1022, 1023)],[(1100, 1101, 1102, 1103), (1110, 1111, 1112, 1113), (1120, 1121, 1122, 1123)]], dtypeint) (1) 根据坐标访问元素或内容,更改访问的内容&#xff0c;array也会更改。“…

C++操作系列(一):MinGW环境安装与配置(无报错版)

本文选择MinGW作为安装对象。 1. 下载MinGW 进入官网&#xff1a;MinGW - Minimalist GNU for Windows download | SourceForge.net 点击File&#xff1a; 划到最下面&#xff1a; &#xfeff; Windows 64位系统下载seh结尾的安装包&#xff1a; 2. 安装MinGW 解压MinGW&am…

力扣第218题“天际线问题”

在本篇文章中&#xff0c;我们将详细解读力扣第218题“天际线问题”。通过学习本篇文章&#xff0c;读者将掌握如何使用扫描线算法和堆来解决这一问题&#xff0c;并了解相关的复杂度分析和模拟面试问答。每种方法都将配以详细的解释&#xff0c;以便于理解。 问题描述 力扣第…

【CSS】深入理解CSS 的steps()函数

在CSS动画中&#xff0c;steps()函数是一个时间函数&#xff0c;它主要用于animation-timing-function属性&#xff0c;以定义动画的步进方式。当你想要动画的某个属性&#xff08;如background-position或transform: translateX()&#xff09;在特定的步骤之间变化时&#xff…

探索 ES6:现代 JavaScript 的新特性

随着 JavaScript 的不断演进&#xff0c;ECMAScript 2015&#xff08;简称 ES6&#xff09;作为 JavaScript 的一次重大更新&#xff0c;带来了许多新特性和语法改进&#xff0c;极大地提升了开发体验和代码质量。本文将详细介绍 ES6 的主要新特性&#xff0c;并展示如何在实际…

NLTK:原理与使用详解

文章目录 NLTK简介NLTK的核心功能1. 文本处理2. 词汇处理3. 语法分析4. 语义分析5. 情感分析 NLTK的使用1. 安装NLTK2. 导入NLTK库3. 下载NLTK数据集4. 文本处理示例5. 情感分析示例 总结 NLTK简介 NLTK是一个开源的Python库&#xff0c;用于处理和分析人类语言数据。它提供了…

扛鼎中国AI搜索,天工凭什么?

人类的创作不会没有瓶颈&#xff0c;但AI的热度可不会消停。 大模型之战依旧精彩&#xff0c;OpenAI选择在Google前一天举行发布会&#xff0c;两家AI企业之间的拉扯赚足了热度。 反观国内&#xff0c;百模大战激发了大家对于科技变革的热切期盼&#xff0c;而如今行业已逐渐…

【操作系统期末速成】 EP01 | 学习笔记(基于五道口一只鸭)

文章目录 一、前言&#x1f680;&#x1f680;&#x1f680;二、正文&#xff1a;☀️☀️☀️1.1 考点一&#xff1a;操作系统的概率及特征 三、总结&#xff1a;&#x1f353;&#x1f353;&#x1f353; 一、前言&#x1f680;&#x1f680;&#x1f680; ☀️ 回报不在行动…

文章浮现之单细胞VDJ的柱状图

应各位老师的需求复现一篇文章的中的某个图 具体复现图5的整个思路图&#xff0c;这里没有原始数据&#xff0c;所以我使用虚拟生产的metadata进行画图 不废话直接上代码&#xff0c;先上python的代码的结果图 import matplotlib.pyplot as plt import numpy as np# 数据&#…