<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:version="2.0"><channel><title>💠 Caelum</title><description>A clean, elegant, and fast static blog template! 🚀 Developed with Astro</description><link>https://c4e-i-um-github-io.vercel.app/</link><language>zh</language><item><title>fread源码解析</title><link>https://c4e-i-um-github-io.vercel.app/blog/fread%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/</link><guid isPermaLink="true">https://c4e-i-um-github-io.vercel.app/blog/fread%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/</guid><description>fread源码解析</description><content:encoded>&lt;h1&gt;前置知识&lt;/h1&gt;
&lt;p&gt;在分析fread函数时，应该先明确一下输入缓冲区是怎么来的。&lt;/p&gt;
&lt;p&gt;首先&lt;strong&gt;fread函数会先将数据读到输入缓冲区中，然后从输入缓冲区中执行memcpy函数，拷贝一定字节的数据到我们指定的内存地址上&lt;/strong&gt;。而这个输入缓冲区是从哪到哪呢？由两个指针分别声明了这片区域的开始和结束，他们分别叫做**_IO_read_base和_IO_read_end，他们之间的区域就是输入缓冲区**。这样看起来似乎还需要一个输出缓冲区，难道需要malloc申请两个堆块来分别表示输入缓冲区和输出缓冲区么？非也，其实&lt;strong&gt;malloc函数自始至终只申请了一个堆块&lt;/strong&gt;，&lt;strong&gt;这个堆块的区域也叫做reserve area&lt;/strong&gt;，而_IO_buf_base和_IO_buf_end两个指针则分别声明了这个reserve area的始末。然后将 _IO_read_ptr; _IO_read_end; _IO_read_base; _IO_write_base; _IO_write_ptr; _IO_write_end;这六个指针全部初始化为了_IO_buf_base的值，现在的输入缓冲区和输出缓冲区还不存在(因为现在 _IO_read_end和 _IO_read_base的值相同)，以输入缓冲区为例，&lt;strong&gt;读入数据时是执行了系统调用read，而此时的数据是在reserve area中，紧接着 _IO_read_end就会加上刚刚读入的数据的个数，那么此时 _IO_read_end和 _IO_read_base的值变的不同了。而现在这二者之间的区域就成为输入缓冲区&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;再提一下_IO_FILE结构体中的一些指针(如下)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;char* _IO_read_ptr;	/* Current read pointer */   
char* _IO_read_end;	/* End of get area. */  
char* _IO_read_base;	/* Start of putback+get area. */  
char* _IO_write_base;	/* Start of put area. */  
char* _IO_write_ptr;	/* Current put pointer. */  
char* _IO_write_end;	/* End of put area. */  
char* _IO_buf_base;	/* Start of reserve area. */  
char* _IO_buf_end;	/* End of reserve area. */
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中_&lt;strong&gt;IO_buf_base 和 _IO_buf_end两个指针的作用分别是标明reserve area的始末&lt;/strong&gt;。&lt;strong&gt;_IO_read_base 和 _IO_read_end两个指针的作用分别是标明输入缓冲区的始末(write那两个指针同理)&lt;/strong&gt;，现在假设有一个30字节的flag文件，然后我连续执行两次fread函数，每次从文件中只读10字节，那么第二次执行fread函数是从哪开始读呢，很明显并不是文件的开始来读取了，而是接着上回fread函数读到的位置，继续读10字节数据。但我们怎么去记录上回fread函数读到哪了呢，这就需要用到**_IO_read_ptr指针了，它是来记录下一次数据应该从输入缓冲区的哪里开始读了。也就是说_IO_read_base 和 _IO_read_ptr 之间的区域是已经使用了的输入缓冲区，而 _IO_read_ptr 和 _IO_read_end之间的区域是输入缓冲区的剩余部分(也就是还未使用部分)**。&lt;/p&gt;
&lt;p&gt;通过上面这两段文字，应该可以对刚学习IO的师傅对_IO_FILE结构体中表示缓冲区位置的指针有一些了解了。&lt;/p&gt;
&lt;p&gt;同时这次还要提到vtable，它是_IO_FILE_plus结构体中的一个字段，也是一个虚表指针。它指向了_IO_jump_t结构体&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct _IO_FILE_plus  
{  
  _IO_FILE file;  
  const struct _IO_jump_t *vtable;  
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于_IO_jump_t结构体，我目前的理解它就是一个跳转表，这里放的都是函数指针。通过不同的偏移获取不同的函数指针，然后将其调用。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct _IO_jump_t  
{  
    JUMP_FIELD(size_t, __dummy);  
    JUMP_FIELD(size_t, __dummy2);  
    JUMP_FIELD(_IO_finish_t, __finish);  
    JUMP_FIELD(_IO_overflow_t, __overflow);  
    JUMP_FIELD(_IO_underflow_t, __underflow);  
    JUMP_FIELD(_IO_underflow_t, __uflow);  
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);  
    /* showmany */  
    JUMP_FIELD(_IO_xsputn_t, __xsputn);  
    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);  
    JUMP_FIELD(_IO_seekoff_t, __seekoff);  
    JUMP_FIELD(_IO_seekpos_t, __seekpos);  
    JUMP_FIELD(_IO_setbuf_t, __setbuf);  
    JUMP_FIELD(_IO_sync_t, __sync);  
    JUMP_FIELD(_IO_doallocate_t, __doallocate);  
    JUMP_FIELD(_IO_read_t, __read);  
    JUMP_FIELD(_IO_write_t, __write);  
    JUMP_FIELD(_IO_seek_t, __seek);  
    JUMP_FIELD(_IO_close_t, __close);  
    JUMP_FIELD(_IO_stat_t, __stat);  
    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);  
    JUMP_FIELD(_IO_imbue_t, __imbue);  
#if 0  
    get_column;  
    set_column;  
#endif  
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;整体流程&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260409222325.png&quot; alt=&quot;Pasted image 20260409222325&quot;&gt;
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260409215903.png&quot; alt=&quot;Pasted image 20260409215903&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260409215924.png&quot; alt=&quot;Pasted image 20260409215924&quot;&gt;&lt;/p&gt;
&lt;h1&gt;源码解析&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;实际fread进入的是_IO_fread&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;quot;libioP.h&amp;quot;

size_t
_IO_fread (void *buf, size_t size, size_t count, FILE *fp)
{
	size_t bytes_requested = size * count;
	size_t bytes_read;
	CHECK_FILE (fp, 0);
	if (bytes_requested == 0)
	return 0;
	_IO_acquire_lock (fp);
	bytes_read = _IO_sgetn (fp, (char *) buf, bytes_requested);
	_IO_release_lock (fp);
	return bytes_requested == bytes_read ? count : bytes_read / size;
}
libc_hidden_def (_IO_fread)
weak_alias (_IO_fread, fread)

# ifndef _IO_MTSAFE_IO
strong_alias (_IO_fread, __fread_unlocked)
libc_hidden_def (__fread_unlocked)
weak_alias (_IO_fread, fread_unlocked)
# endif
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260408104521.png&quot; alt=&quot;Pasted image 20260408104521&quot;&gt;&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;进入_IO_sgetn
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260408104641.png&quot; alt=&quot;Pasted image 20260408104641&quot;&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;size_t
_IO_sgetn (FILE *fp, void *data, size_t n)
{
	/* FIXME handle putback buffer here! */
	return _IO_XSGETN (fp, data, n);
}
libc_hidden_def (_IO_sgetn)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;IO_JUMPS_FUNC(THIS)返回的是_IO_jump_t地址,对应FILE结构体的vtable字段,由于JUMP2第一个参数就是__xsgetn，所以-&amp;gt;FUNC访问的就是_IO_jump_t结构体中__xsgetn,采用的是vtable + offset的形式(借此可以伪造vtable,实现任意合法执行)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define _IO_XSGETN(FP, DATA, N) JUMP2 (__xsgetn, FP, DATA, N)  
#define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)-&amp;gt;FUNC) (THIS, X1, X2)  
# define _IO_JUMPS_FUNC(THIS) (IO_validate_vtable (_IO_JUMPS_FILE_plus (THIS)))
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
	uintptr_t ptr = (uintptr_t) vtable;
	uintptr_t offset = ptr - (uintptr_t) &amp;amp;__io_vtables;
	if (__glibc_unlikely (offset &amp;gt;= IO_VTABLES_LEN))
	/* The vtable pointer is not in the expected section. Use the
	slow path, which will terminate the process if necessary. */
	_IO_vtable_check ();
	return vtable;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void attribute_hidden
_IO_vtable_check (void)
{
#ifdef SHARED
	/* Honor the compatibility flag. */
	void (*flag) (void) = atomic_load_relaxed (&amp;amp;IO_accept_foreign_vtables);
	PTR_DEMANGLE (flag);
	if (flag == &amp;amp;_IO_vtable_check)
	return;
  
/* In case this libc copy is in a non-default namespace, we always
need to accept foreign vtables because there is always a
possibility that FILE * objects are passed across the linking
boundary. */
	{
		Dl_info di;
		struct link_map *l;
		if (!rtld_active ()
		|| (_dl_addr (_IO_vtable_check, &amp;amp;di, &amp;amp;l, NULL) != 0
		&amp;amp;&amp;amp; l-&amp;gt;l_ns != LM_ID_BASE))
		return;
	}

  
#else /* !SHARED */
	/* We cannot perform vtable validation in the static dlopen case
	because FILE * handles might be passed back and forth across the
	boundary. Therefore, we disable checking in this case. */
	if (__dlopen != NULL)
		return;
#endif

__libc_fatal (&amp;quot;Fatal error: glibc detected an invalid stdio handle\n&amp;quot;);

}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;0x7ffff7e26661 &amp;lt;_IO_sgetn+33&amp;gt;              jmp    qword ptr [rax + 0x40]      &amp;lt;__GI__IO_file_xsgetn&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260408150628.png&quot; alt=&quot;Pasted image 20260408150628&quot;&gt;
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260408150513.png&quot; alt=&quot;Pasted image 20260408150513&quot;&gt;&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;进入__GI__IO_file_xsgetn
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260408111344.png&quot; alt=&quot;Pasted image 20260408111344&quot;&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;size_t
_IO_file_xsgetn (FILE *fp, void *data, size_t n)
{
	size_t want, have;
	ssize_t count;
	char *s = data;
	want = n;
	
	
	/*第一部分*/
	//如果没有申请buf,_IO_doallocbuf内部malloc一个
	if (fp-&amp;gt;_IO_buf_base == NULL)
	{
	/* Maybe we already have a push back pointer. */
		if (fp-&amp;gt;_IO_save_base != NULL)
		{
			_IO_free_backup_buf (fp, fp-&amp;gt;_IO_save_base);
			fp-&amp;gt;_flags &amp;amp;= ~_IO_IN_BACKUP;
		}
	_IO_doallocbuf (fp);
	}
	
	while (want &amp;gt; 0)
	{   /*第二部分*/
		have = fp-&amp;gt;_IO_read_end - fp-&amp;gt;_IO_read_ptr;
		if (want &amp;lt;= have)
		{
			memcpy (s, fp-&amp;gt;_IO_read_ptr, want);
			fp-&amp;gt;_IO_read_ptr += want;
			want = 0;
		}
		else
		{
			if (have &amp;gt; 0)
			{
				s = __mempcpy (s, fp-&amp;gt;_IO_read_ptr, have);
				want -= have;
				fp-&amp;gt;_IO_read_ptr += have;
			}
			
			/*第三部分*/
			/* Check for backup and repeat */
			if (_IO_in_backup (fp))
			{
				_IO_switch_to_main_get_area (fp);
				continue;
			}
		
		
			/* If we now want less than a buffer, underflow and repeat
			the copy. Otherwise, _IO_SYSREAD directly to
			the user buffer. */
			/*第四部分*/
			if (fp-&amp;gt;_IO_buf_base
			&amp;amp;&amp;amp; want &amp;lt; (size_t) (fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base))
			{
				if (__underflow (fp) == EOF)
				break;
				continue;
			}
			
			/* These must be set before the sysread as we might longjmp out
			waiting for input. */
			_IO_setg (fp, fp-&amp;gt;_IO_buf_base, fp-&amp;gt;_IO_buf_base, fp-&amp;gt;_IO_buf_base);
			_IO_setp (fp, fp-&amp;gt;_IO_buf_base, fp-&amp;gt;_IO_buf_base);
			
			/* Try to maintain alignment: read a whole number of blocks. */
			count = want;
			if (fp-&amp;gt;_IO_buf_base)
			{
				size_t block_size = fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base;
				if (block_size &amp;gt;= 128)
				count -= want % block_size;
			}
			
			count = _IO_SYSREAD (fp, s, count);
			if (count &amp;lt;= 0)
			{
				if (count == 0)
					fp-&amp;gt;_flags |= _IO_EOF_SEEN;
				else
					fp-&amp;gt;_flags |= _IO_ERR_SEEN;
				break;
			}
			
			s += count;
			want -= count;
			if (fp-&amp;gt;_offset != _IO_pos_BAD)
				_IO_pos_adjust (fp-&amp;gt;_offset, count);
		}
	}
	
	return n - want;
}
libc_hidden_def (_IO_file_xsgetn)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第一部分
3.1 进入_IO_doallocbuf
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260409222749.png&quot; alt=&quot;Pasted image 20260409222749&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void
_IO_doallocbuf (FILE *fp)
{
	if (fp-&amp;gt;_IO_buf_base)
	return;
	if (!(fp-&amp;gt;_flags &amp;amp; _IO_UNBUFFERED) || fp-&amp;gt;_mode &amp;gt; 0)
	if (_IO_DOALLOCATE (fp) != EOF)
	return;
	_IO_setb (fp, fp-&amp;gt;_shortbuf, fp-&amp;gt;_shortbuf+1, 0);
}
libc_hidden_def (_IO_doallocbuf)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3.2通过vtable进入_IO_file_doallocate
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260409223643.png&quot; alt=&quot;Pasted image 20260409223643&quot;&gt;
3.3进入vtable中的_IO_file_stat函数，也是通过vtable+offset
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260408145106.png&quot; alt=&quot;Pasted image 20260408145106&quot;&gt;
3.4调用fstat64，这个系统调用是来获取文件状态，并且初始化st结构体的
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260408145224.png&quot; alt=&quot;Pasted image 20260408145224&quot;&gt;
可以看到此时的st_blksize为4096
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260408150041.png&quot; alt=&quot;Pasted image 20260408150041&quot;&gt;
3.5而这个st_blksize也就是接下来malloc申请的内存大小
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260408150213.png&quot; alt=&quot;Pasted image 20260408150213&quot;&gt;
3.6调用_IO_setb ,这个函数主要就是对_IO_buf_base和_IO_buf_end指针进行赋值&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void
_IO_setb (FILE *f, char *b, char *eb, int a)
{
	if (f-&amp;gt;_IO_buf_base &amp;amp;&amp;amp; !(f-&amp;gt;_flags &amp;amp; _IO_USER_BUF))
		free (f-&amp;gt;_IO_buf_base);
	f-&amp;gt;_IO_buf_base = b;
	f-&amp;gt;_IO_buf_end = eb;
	if (a)
		f-&amp;gt;_flags &amp;amp;= ~_IO_USER_BUF;
	else
		f-&amp;gt;_flags |= _IO_USER_BUF;
}
libc_hidden_def (_IO_setb)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行前
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260408151353.png&quot; alt=&quot;Pasted image 20260408151353&quot;&gt;
执行后
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260408155610.png&quot; alt=&quot;Pasted image 20260408155610&quot;&gt;
第二部分
have是剩余的缓冲区大小,want是需要读入的大小
如果want小于等于have直接memcpy
如果have还有剩余但是want大于have，把能读的先读完&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;		have = fp-&amp;gt;_IO_read_end - fp-&amp;gt;_IO_read_ptr;
		if (want &amp;lt;= have)
		{
			memcpy (s, fp-&amp;gt;_IO_read_ptr, want);
			fp-&amp;gt;_IO_read_ptr += want;
			want = 0;
		}
		else
		{
			if (have &amp;gt; 0)
			{
				s = __mempcpy (s, fp-&amp;gt;_IO_read_ptr, have);
				want -= have;
				fp-&amp;gt;_IO_read_ptr += have;
			}
		
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第三部分&lt;/p&gt;
&lt;p&gt;如果当前处于备用的缓冲区，切换为主缓冲区&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;	/* Check for backup and repeat */
			if (_IO_in_backup (fp))
			{
				_IO_switch_to_main_get_area (fp);
				continue;
			}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define _IO_in_backup(fp) ((fp)-&amp;gt;_flags &amp;amp; _IO_IN_BACKUP)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void
_IO_switch_to_main_get_area (FILE *fp)
{
	char *tmp;
	fp-&amp;gt;_flags &amp;amp;= ~_IO_IN_BACKUP;
	/* Swap _IO_read_end and _IO_save_end. */
	tmp = fp-&amp;gt;_IO_read_end;
	fp-&amp;gt;_IO_read_end = fp-&amp;gt;_IO_save_end;
	fp-&amp;gt;_IO_save_end= tmp;
	/* Swap _IO_read_base and _IO_save_base. */
	tmp = fp-&amp;gt;_IO_read_base;
	fp-&amp;gt;_IO_read_base = fp-&amp;gt;_IO_save_base;
	fp-&amp;gt;_IO_save_base = tmp;
	/* Set _IO_read_ptr. */
	fp-&amp;gt;_IO_read_ptr = fp-&amp;gt;_IO_read_base;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; 第四部分 __underflow
 它先是经过一些检查后，去调用了vtable中的_IO_file_underflow函数&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int
__underflow (FILE *fp)
{
	if (_IO_vtable_offset (fp) == 0 &amp;amp;&amp;amp; _IO_fwide (fp, -1) != -1)
		return EOF;
	if (fp-&amp;gt;_mode == 0)
		_IO_fwide (fp, -1);
	if (_IO_in_put_mode (fp))
		if (_IO_switch_to_get_mode (fp) == EOF)
			return EOF;
	if (fp-&amp;gt;_IO_read_ptr &amp;lt; fp-&amp;gt;_IO_read_end)
		return *(unsigned char *) fp-&amp;gt;_IO_read_ptr;
	if (_IO_in_backup (fp))
	{
		_IO_switch_to_main_get_area (fp);
		if (fp-&amp;gt;_IO_read_ptr &amp;lt; fp-&amp;gt;_IO_read_end)
			return *(unsigned char *) fp-&amp;gt;_IO_read_ptr;
		}
	if (_IO_have_markers (fp))
	{
		if (save_for_backup (fp, fp-&amp;gt;_IO_read_end))
			return EOF;
	}
	else if (_IO_have_backup (fp))
		_IO_free_backup_area (fp);
	return _IO_UNDERFLOW (fp);
}
libc_hidden_def (__underflow)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在最后_IO_UNDERFLOW，通过vtable调用了_IO_file_underflow函数
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260408211800.png&quot; alt=&quot;Pasted image 20260408211800&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int
_IO_new_file_underflow (FILE *fp)
{
	ssize_t count;
	/* C99 requires EOF to be &amp;quot;sticky&amp;quot;. */
	if (fp-&amp;gt;_flags &amp;amp; _IO_EOF_SEEN)
		return EOF;
	if (fp-&amp;gt;_flags &amp;amp; _IO_NO_READS)
	{
		fp-&amp;gt;_flags |= _IO_ERR_SEEN;
		__set_errno (EBADF);
		return EOF;
	}
	if (fp-&amp;gt;_IO_read_ptr &amp;lt; fp-&amp;gt;_IO_read_end)
		return *(unsigned char *) fp-&amp;gt;_IO_read_ptr;
	if (fp-&amp;gt;_IO_buf_base == NULL)
	{
		/* Maybe we already have a push back pointer. */
		if (fp-&amp;gt;_IO_save_base != NULL)
		{
			_IO_free_backup_buf (fp, fp-&amp;gt;_IO_save_base);
			fp-&amp;gt;_flags &amp;amp;= ~_IO_IN_BACKUP;
		}
		_IO_doallocbuf (fp);
	}
	/* FIXME This can/should be moved to genops ?? */
	if (fp-&amp;gt;_flags &amp;amp; (_IO_LINE_BUF|_IO_UNBUFFERED))
	{
	
	/* We used to flush all line-buffered stream. This really isn&amp;#39;t
	required by any standard. My recollection is that
	traditional Unix systems did this for stdout. stderr better
	not be line buffered. So we do just that here
	explicitly. --drepper */
	
		_IO_acquire_lock (stdout);
		if ((stdout-&amp;gt;_flags &amp;amp; (_IO_LINKED | _IO_NO_WRITES | _IO_LINE_BUF))
		== (_IO_LINKED | _IO_LINE_BUF))
			_IO_OVERFLOW (stdout, EOF);
		_IO_release_lock (stdout);
	}
	
	_IO_switch_to_get_mode (fp);
	
	/* This is very tricky. We have to adjust those
	pointers before we call _IO_SYSREAD () since
	we may longjump () out while waiting for
	input. Those pointers may be screwed up. H.J. */
	
	fp-&amp;gt;_IO_read_base = fp-&amp;gt;_IO_read_ptr = fp-&amp;gt;_IO_buf_base;
	fp-&amp;gt;_IO_read_end = fp-&amp;gt;_IO_buf_base;
	fp-&amp;gt;_IO_write_base = fp-&amp;gt;_IO_write_ptr = fp-&amp;gt;_IO_write_end
	= fp-&amp;gt;_IO_buf_base;
	
	count = _IO_SYSREAD (fp, fp-&amp;gt;_IO_buf_base,
	fp-&amp;gt;_IO_buf_end - fp-&amp;gt;_IO_buf_base);
	if (count &amp;lt;= 0)
	{
		if (count == 0)
			fp-&amp;gt;_flags |= _IO_EOF_SEEN;
		else
			fp-&amp;gt;_flags |= _IO_ERR_SEEN, count = 0;
	}
	fp-&amp;gt;_IO_read_end += count;
	if (count == 0)
	{
	
	/* If a stream is read to EOF, the calling application may switch active
	handles. As a result, our offset cache would no longer be valid, so
	unset it. */
	
		fp-&amp;gt;_offset = _IO_pos_BAD;
		return EOF;
	}
	if (fp-&amp;gt;_offset != _IO_pos_BAD)
		_IO_pos_adjust (fp-&amp;gt;_offset, count);
	return *(unsigned char *) fp-&amp;gt;_IO_read_ptr;
}

libc_hidden_ver (_IO_new_file_underflow, _IO_file_underflow)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上来就是很多检查，不过根据程序当前的状态，直接跳过了前面的检查。先去执行了_IO_switch_to_get_mode 函数
可以看到是给_IO_read_base赋值，其他也赋值了不过都是0&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int
_IO_switch_to_get_mode (FILE *fp)
{
	if (fp-&amp;gt;_IO_write_ptr &amp;gt; fp-&amp;gt;_IO_write_base)
		if (_IO_OVERFLOW (fp, EOF) == EOF)
			return EOF;
	if (_IO_in_backup (fp))
		fp-&amp;gt;_IO_read_base = fp-&amp;gt;_IO_backup_base;
	else
	{
		fp-&amp;gt;_IO_read_base = fp-&amp;gt;_IO_buf_base;
		if (fp-&amp;gt;_IO_write_ptr &amp;gt; fp-&amp;gt;_IO_read_end)
			fp-&amp;gt;_IO_read_end = fp-&amp;gt;_IO_write_ptr;
	}
	fp-&amp;gt;_IO_read_ptr = fp-&amp;gt;_IO_write_ptr;
	  
	fp-&amp;gt;_IO_write_base = fp-&amp;gt;_IO_write_ptr = fp-&amp;gt;_IO_write_end = fp-&amp;gt;_IO_read_ptr;
	
	fp-&amp;gt;_flags &amp;amp;= ~_IO_CURRENTLY_PUTTING;
	return 0;
}

libc_hidden_def (_IO_switch_to_get_mode)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行前
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260408213537.png&quot; alt=&quot;Pasted image 20260408213537&quot;&gt;
执行后
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260408214114.png&quot; alt=&quot;Pasted image 20260408214114&quot;&gt;&lt;/p&gt;
&lt;p&gt;接下来就是赋值&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;fp-&amp;gt;_IO_read_base = fp-&amp;gt;_IO_read_ptr = fp-&amp;gt;_IO_buf_base;
	fp-&amp;gt;_IO_read_end = fp-&amp;gt;_IO_buf_base;
	fp-&amp;gt;_IO_write_base = fp-&amp;gt;_IO_write_ptr = fp-&amp;gt;_IO_write_end
	= fp-&amp;gt;_IO_buf_base;
	
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行后
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260408214358.png&quot; alt=&quot;Pasted image 20260408214358&quot;&gt;
然后是执行，通过vtavle进入_IO_file_read
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260408214540.png&quot; alt=&quot;Pasted image 20260408214540&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;count = _IO_SYSREAD (fp, fp-&amp;gt;_IO_buf_base,
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;内部调用了read，读入_IO_read_ptr
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260408214735.png&quot; alt=&quot;Pasted image 20260408214735&quot;&gt;
读入后，现在_IO_read_end没有变
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260408214909.png&quot; alt=&quot;Pasted image 20260408214909&quot;&gt;
执行到&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;fp-&amp;gt;_IO_read_end += count;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;的时候才被改变
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260408215248.png&quot; alt=&quot;Pasted image 20260408215248&quot;&gt;
之后再次循环，不断改变_IO_read_ptr的值
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fread/Pasted%20image%2020260408215408.png&quot; alt=&quot;Pasted image 20260408215408&quot;&gt;
直到结束，退出&lt;/p&gt;
 &lt;blockquote&gt;This rendering was automatically generated by Frosti Feed and may have formatting issues. For the best experience, please visit: &lt;a href=&quot;https://c4e-i-um-github-io.vercel.app/blog/fread%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/&quot;&gt;https://c4e-i-um-github-io.vercel.app/blog/fread%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/&lt;/a&gt;&lt;/blockquote&gt;</content:encoded><dc:creator>Caelum</dc:creator><pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate></item><item><title>fopen源码解析</title><link>https://c4e-i-um-github-io.vercel.app/blog/fopen%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/</link><guid isPermaLink="true">https://c4e-i-um-github-io.vercel.app/blog/fopen%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/</guid><description>fopen源码解析</description><content:encoded>&lt;h1&gt;前置知识&lt;/h1&gt;
&lt;h2&gt;_IO_FILE_plus 结构体&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct _IO_FILE_plus
{
  _IO_FILE file;
  const struct _IO_jump_t *vtable;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;_IO_FILE结构体&lt;/h1&gt;
&lt;p&gt;先说_IO_FILE结构体，该结构体就是标准IO库中用来描述文件的结构，在程序执行fopen函数时会创建该结构，并分配在堆中。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct _IO_FILE {
  int _flags;		/* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  char* _IO_read_ptr;	/* Current read pointer */ 
  char* _IO_read_end;	/* End of get area. */
  char* _IO_read_base;	/* Start of putback+get area. */
  char* _IO_write_base;	/* Start of put area. */
  char* _IO_write_ptr;	/* Current put pointer. */
  char* _IO_write_end;	/* End of put area. */
  char* _IO_buf_base;	/* Start of reserve area. */
  char* _IO_buf_end;	/* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
#if 0
  int _blksize;
#else
  int _flags2;
#endif
  _IO_off_t _old_offset; /* This used to be _offset but it&amp;#39;s too small.  */

#define __HAVE_COLUMN /* temporary */
  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  /*  char* _save_gptr;  char* _save_egptr; */

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;调试源码&lt;/h1&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;

int main() {
	FILE *file = fopen(&amp;quot;example.txt&amp;quot;, &amp;quot;r&amp;quot;);
	if (file == NULL) {
		perror(&amp;quot;Error opening file&amp;quot;);
		return EXIT_FAILURE;
	}
	
	fclose(file);
	return EXIT_SUCCESS;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;总体概述&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fopen/Pasted%20image%2020260403221324.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fopen/Pasted%20image%2020260404082647.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h1&gt;源码解析&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;打断点到fopen&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;fopen实际上进入的是_IO_new_fopen函数&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;FILE *
_IO_new_fopen (const char *filename, const char *mode)
{
	return __fopen_internal (filename, mode, 1);
}

strong_alias (_IO_new_fopen, __new_fopen)
versioned_symbol (libc, _IO_new_fopen, _IO_fopen, GLIBC_2_1);
versioned_symbol (libc, __new_fopen, fopen, GLIBC_2_1);

# if !defined O_LARGEFILE || O_LARGEFILE == 0
  weak_alias (_IO_new_fopen, _IO_fopen64)
  weak_alias (_IO_new_fopen, fopen64)
# endif
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fopen/Pasted%20image%2020260403203655.png&quot; alt=&quot;alt text&quot;&gt;
2.  _IO_new_fopen函数进入了__fopen_internal
这个函数是对_IO_new_file_init_internal的封装，会在这里申请一个locked_FILE结构体&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;FILE *
__fopen_internal (const char *filename, const char *mode, int is32)
{
	struct locked_FILE
	{
		struct _IO_FILE_plus fp;
#ifdef _IO_MTSAFE_IO
		_IO_lock_t lock;
#endif
		struct _IO_wide_data wd;
	} *new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE));
	
	  

	if (new_f == NULL)
		return NULL;
#ifdef _IO_MTSAFE_IO
	new_f-&amp;gt;fp.file._lock = &amp;amp;new_f-&amp;gt;lock;
#endif
	_IO_no_init (&amp;amp;new_f-&amp;gt;fp.file, 0, 0, &amp;amp;new_f-&amp;gt;wd, &amp;amp;_IO_wfile_jumps);
	_IO_JUMPS (&amp;amp;new_f-&amp;gt;fp) = &amp;amp;_IO_file_jumps;
	_IO_new_file_init_internal (&amp;amp;new_f-&amp;gt;fp);
	if (_IO_file_fopen ((FILE *) new_f, filename, mode, is32) != NULL)
		return __fopen_maybe_mmap (&amp;amp;new_f-&amp;gt;fp.file);
	  
	_IO_un_link (&amp;amp;new_f-&amp;gt;fp);
	free (new_f);
	return NULL;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;pwndbg&amp;gt; ptype struct locked_FILE 
type = struct locked_FILE {
    struct _IO_FILE_plus fp;
    _IO_lock_t lock;
    struct _IO_wide_data wd;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;__fopen_internal进入_IO_no_init&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可以发现是初始化FILE结构体，分为两个部分_IO_old_init和wide_data的a初始化&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void
_IO_no_init (FILE *fp, int flags, int orientation,
struct _IO_wide_data *wd, const struct _IO_jump_t *jmp)
{
	_IO_old_init (fp, flags);
	fp-&amp;gt;_mode = orientation;
	if (orientation &amp;gt;= 0)
{

	fp-&amp;gt;_wide_data = wd;
	fp-&amp;gt;_wide_data-&amp;gt;_IO_buf_base = NULL;
	fp-&amp;gt;_wide_data-&amp;gt;_IO_buf_end = NULL;
	fp-&amp;gt;_wide_data-&amp;gt;_IO_read_base = NULL;
	fp-&amp;gt;_wide_data-&amp;gt;_IO_read_ptr = NULL;
	fp-&amp;gt;_wide_data-&amp;gt;_IO_read_end = NULL;
	fp-&amp;gt;_wide_data-&amp;gt;_IO_write_base = NULL;
	fp-&amp;gt;_wide_data-&amp;gt;_IO_write_ptr = NULL;
	fp-&amp;gt;_wide_data-&amp;gt;_IO_write_end = NULL;
	fp-&amp;gt;_wide_data-&amp;gt;_IO_save_base = NULL;
	fp-&amp;gt;_wide_data-&amp;gt;_IO_backup_base = NULL;
	fp-&amp;gt;_wide_data-&amp;gt;_IO_save_end = NULL;
	
	fp-&amp;gt;_wide_data-&amp;gt;_wide_vtable = jmp;

}

else
	/* Cause predictable crash when a wide function is called on a byte
	stream. */
	fp-&amp;gt;_wide_data = (struct _IO_wide_data *) -1L;
	fp-&amp;gt;_freeres_list = NULL;
	fp-&amp;gt;_total_written = 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fopen/Pasted%20image%2020260403205315.png&quot; alt=&quot;alt text&quot;&gt; 
初始化之前
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fopen/Pasted%20image%2020260403205351.png&quot; alt=&quot;alt text&quot;&gt;
4._IO_no_init 进入_IO_old_init&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void
_IO_old_init (FILE *fp, int flags)
{
	fp-&amp;gt;_flags = _IO_MAGIC|flags;  /*_IO_MAGIC是魔数标志位，表明这个FILE对象是有效的*/
	fp-&amp;gt;_flags2 = 0;
	if (stdio_needs_locking)
	fp-&amp;gt;_flags2 |= _IO_FLAGS2_NEED_LOCK;
	fp-&amp;gt;_IO_buf_base = NULL;
	fp-&amp;gt;_IO_buf_end = NULL;
	fp-&amp;gt;_IO_read_base = NULL;
	fp-&amp;gt;_IO_read_ptr = NULL;
	fp-&amp;gt;_IO_read_end = NULL;
	fp-&amp;gt;_IO_write_base = NULL;
	fp-&amp;gt;_IO_write_ptr = NULL;
	fp-&amp;gt;_IO_write_end = NULL;
	fp-&amp;gt;_chain = NULL; /* Not necessary. */
	
	fp-&amp;gt;_IO_save_base = NULL;
	fp-&amp;gt;_IO_backup_base = NULL;
	fp-&amp;gt;_IO_save_end = NULL;
	fp-&amp;gt;_markers = NULL;
	fp-&amp;gt;_cur_column = 0;
	#if _IO_JUMPS_OFFSET
	fp-&amp;gt;_vtable_offset = 0;
#endif
#ifdef _IO_MTSAFE_IO
	if (fp-&amp;gt;_lock != NULL)
	_IO_lock_init (*fp-&amp;gt;_lock);
#endif
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;经过_IO_old_init初始化后
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fopen/Pasted%20image%2020260403205933.png&quot; alt=&quot;alt text&quot;&gt;
5. 设置_mode，这个字段代表的是流的字符方向/宽窄方向&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void 
   587 _IO_no_init (FILE *fp, int flags, int orientation,
   588              struct _IO_wide_data *wd, const struct _IO_jump_t *jmp)
   589 {
   590   _IO_old_init (fp, flags);
 ► 591   fp-&amp;gt;_mode = orientation;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;`fp-&amp;gt;_mode` 不是打开模式 `&amp;quot;r&amp;quot; / &amp;quot;w&amp;quot; / &amp;quot;a&amp;quot;`，而是 **流的字符方向/宽窄方向**：

- `0`：还没定向，未决定是字节流还是宽字符流
- `&amp;gt; 0`：宽字符流 wide-oriented
- `&amp;lt; 0`：字节流 byte-oriented
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fopen/Pasted%20image%2020260403210642.png&quot; alt=&quot;alt text&quot;&gt;
6. 初始化宽字符数据_wide_data
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fopen/Pasted%20image%2020260403210941.png&quot; alt=&quot;alt text&quot;&gt;
7. 返回__fopen_internal
8. 执行_IO_JUMPS (&amp;amp;new_f-&amp;gt;fp) = &amp;amp;_IO_file_jumps;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define _IO_JUMPS(THIS) (THIS)-&amp;gt;vtable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;new_f-&amp;gt;fp.vtable = &amp;amp;_IO_file_jumps;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行之前&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;pwndbg&amp;gt; p *new_f
$5 = {
  fp = {
    file = {
      _flags = -72548352,
      _IO_read_ptr = 0x0,
      _IO_read_end = 0x0,
      _IO_read_base = 0x0,
      _IO_write_base = 0x0,
      _IO_write_ptr = 0x0,
      _IO_write_end = 0x0,
      _IO_buf_base = 0x0,
      _IO_buf_end = 0x0,
      _IO_save_base = 0x0,
      _IO_backup_base = 0x0,
      _IO_save_end = 0x0,
      _markers = 0x0,
      _chain = 0x0,
      _fileno = 0,
      _flags2 = 0,
      _short_backupbuf = &amp;quot;&amp;quot;,
      _old_offset = 0,
      _cur_column = 0,
      _vtable_offset = 0 &amp;#39;\000&amp;#39;,
      _shortbuf = &amp;quot;&amp;quot;,
      _lock = 0x5555555590f0,
      _offset = 0,
      _codecvt = 0x0,
      _wide_data = 0x555555559100,
      _freeres_list = 0x0,
      _freeres_buf = 0x0,
      _prevchain = 0x0,
      _mode = 0,
      _unused3 = 0,
      _total_written = 0,
      _unused2 = &amp;quot;\000\000\000\000\000\000\000&amp;quot;
    },
    vtable = 0x0    /*这里还没有*/
  },
  lock = {
    lock = 0,
    cnt = 0,
    owner = 0x0
  },
  wd = {
    _IO_read_ptr = 0x0,
    _IO_read_end = 0x0,
    _IO_read_base = 0x0,
    _IO_write_base = 0x0,
    _IO_write_ptr = 0x0,
    _IO_write_end = 0x0,
    _IO_buf_base = 0x0,
    _IO_buf_end = 0x0,
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _IO_state = {
      __count = 0,
      __value = {
        __wch = 0,
        __wchb = &amp;quot;\000\000\000&amp;quot;
      }
    },
    _IO_last_state = {
      __count = 0,
      __value = {
        __wch = 0,
        __wchb = &amp;quot;\000\000\000&amp;quot;
      }
    },
    _codecvt = {
      __cd_in = {
        step = 0x0,
        step_data = {
          __outbuf = 0x0,
          __outbufend = 0x0,
          __flags = 0,
          __invocation_counter = 0,
          __internal_use = 0,
          __statep = 0x0,
          __state = {
            __count = 0,
            __value = {
              __wch = 0,
              __wchb = &amp;quot;\000\000\000&amp;quot;
            }
          }
        }
      },
      __cd_out = {
        step = 0x0,
        step_data = {
          __outbuf = 0x0,
          __outbufend = 0x0,
          __flags = 0,
          __invocation_counter = 0,
          __internal_use = 0,
          __statep = 0x0,
          __state = {
            __count = 0,
            __value = {
              __wch = 0,
              __wchb = &amp;quot;\000\000\000&amp;quot;
            }
          }
        }
      }
    },
    _shortbuf = L&amp;quot;&amp;quot;,
    _wide_vtable = 0x7ffff7f7c228 &amp;lt;_IO_wfile_jumps&amp;gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行后&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt; fp = {
    file = {
      _flags = -72548352,
      _IO_read_ptr = 0x0,
      _IO_read_end = 0x0,
      _IO_read_base = 0x0,
      _IO_write_base = 0x0,
      _IO_write_ptr = 0x0,
      _IO_write_end = 0x0,
      _IO_buf_base = 0x0,
      _IO_buf_end = 0x0,
      _IO_save_base = 0x0,
      _IO_backup_base = 0x0,
      _IO_save_end = 0x0,
      _markers = 0x0,
      _chain = 0x0,
      _fileno = 0,
      _flags2 = 0,
      _short_backupbuf = &amp;quot;&amp;quot;,
      _old_offset = 0,
      _cur_column = 0,
      _vtable_offset = 0 &amp;#39;\000&amp;#39;,
      _shortbuf = &amp;quot;&amp;quot;,
      _lock = 0x5555555590f0,
      _offset = 0,
      _codecvt = 0x0,
      _wide_data = 0x555555559100,
      _freeres_list = 0x0,
      _freeres_buf = 0x0,
      _prevchain = 0x0,
      _mode = 0,
      _unused3 = 0,
      _total_written = 0,
      _unused2 = &amp;quot;\000\000\000\000\000\000\000&amp;quot;
    },
    vtable = 0x7ffff7f7c030 &amp;lt;_IO_file_jumps&amp;gt;
  },
  ...
  ...  
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;9&quot;&gt;
&lt;li&gt;进入_IO_new_file_init_internal&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void
_IO_new_file_init_internal (struct _IO_FILE_plus *fp)
{
	/* POSIX.1 allows another file handle to be used to change the position
	of our file descriptor. Hence we actually don&amp;#39;t know the actual
	position before we do the first fseek (and until a following fflush). */
	fp-&amp;gt;file._offset = _IO_pos_BAD;
	fp-&amp;gt;file._flags |= CLOSED_FILEBUF_FLAGS;
	_IO_link_in (fp);
	fp-&amp;gt;file._fileno = -1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;10&quot;&gt;
&lt;li&gt;进入 _IO_link_in
主要是把新的_IO_FILE_plus结构体加入_IO_list_all&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt; void
_IO_link_in (struct _IO_FILE_plus *fp)
{
	if ((fp-&amp;gt;file._flags &amp;amp; _IO_LINKED) == 0)
	{
		fp-&amp;gt;file._flags |= _IO_LINKED;
#ifdef _IO_MTSAFE_IO
		_IO_cleanup_region_start_noarg (flush_cleanup);
		_IO_lock_lock (list_all_lock);
		run_fp = (FILE *) fp;
		_IO_flockfile ((FILE *) fp);
#endif
		fp-&amp;gt;file._chain = (FILE *) _IO_list_all;
		if (_IO_vtable_offset ((FILE *) fp) == 0)
		{
			fp-&amp;gt;file._prevchain = (FILE **) &amp;amp;_IO_list_all;
			if (_IO_list_all != NULL)
			_IO_list_all-&amp;gt;file._prevchain = &amp;amp;fp-&amp;gt;file._chain;
		}
			_IO_list_all = fp;   
#ifdef _IO_MTSAFE_IO
			_IO_funlockfile ((FILE *) fp);
			run_fp = NULL;
			_IO_lock_unlock (list_all_lock);
			_IO_cleanup_region_end (0);
#endif
		}
	}
libc_hidden_def (_IO_link_in)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;11&quot;&gt;
&lt;li&gt;执行_IO_file_fopen&lt;br&gt; 基本是根据参数设置各种标志位&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;FILE *
_IO_new_file_fopen (FILE *fp, const char *filename, const char *mode,
int is32not64)
{
	int oflags = 0, omode;
	int read_write;
	int oprot = 0666;
	int i;
	FILE *result;
	const char *cs;
	const char *last_recognized;
	  
	if (_IO_file_is_open (fp))
	return NULL;
	switch (*mode)
	{
		case &amp;#39;r&amp;#39;:
		omode = O_RDONLY;
		read_write = _IO_NO_WRITES;
		break;
		case &amp;#39;w&amp;#39;:
		omode = O_WRONLY;
		oflags = O_CREAT|O_TRUNC;
		read_write = _IO_NO_READS;
		break;
		case &amp;#39;a&amp;#39;:
		omode = O_WRONLY;
		oflags = O_CREAT|O_APPEND;
		read_write = _IO_NO_READS|_IO_IS_APPENDING;
		break;
		default:
		__set_errno (EINVAL);
		return NULL;
	}
last_recognized = mode;
for (i = 1; i &amp;lt; 7; ++i)
{
	switch (*++mode)
	{
	case &amp;#39;\0&amp;#39;:
	case &amp;#39;,&amp;#39;:
		break;
	case &amp;#39;+&amp;#39;:
		omode = O_RDWR;
		read_write &amp;amp;= _IO_IS_APPENDING;
		last_recognized = mode;
		continue;
	case &amp;#39;x&amp;#39;:
		oflags |= O_EXCL;
		last_recognized = mode;
		continue;
	case &amp;#39;b&amp;#39;:
		last_recognized = mode;
		continue;
	case &amp;#39;m&amp;#39;:
		fp-&amp;gt;_flags2 |= _IO_FLAGS2_MMAP;
		continue;
	case &amp;#39;c&amp;#39;:
		fp-&amp;gt;_flags2 |= _IO_FLAGS2_NOTCANCEL;
		continue;
	case &amp;#39;e&amp;#39;:
		oflags |= O_CLOEXEC;
		fp-&amp;gt;_flags2 |= _IO_FLAGS2_CLOEXEC;
		continue;
	default:
	/* Ignore. */
		continue;
	}
	break;
	}

result = _IO_file_open (fp, filename, omode|oflags, oprot, read_write,
is32not64);
  
	if (result != NULL)
	{
		/* Test whether the mode string specifies the conversion. */
		cs = strstr (last_recognized + 1, &amp;quot;,ccs=&amp;quot;);
		if (cs != NULL)
		{
		
				/* Yep. Load the appropriate conversions and set the orientation
				to wide. */
				struct gconv_fcts fcts;
				struct _IO_codecvt *cc;
				char *endp = __strchrnul (cs + 5, &amp;#39;,&amp;#39;);
				char *ccs = malloc (endp - (cs + 5) + 3);
				
			if (ccs == NULL)
			{
				int malloc_err = errno; /* Whatever malloc failed with. */
				(void) _IO_file_close_it (fp);
				__set_errno (malloc_err);
				return NULL;
			}
			
			  
			
			*((char *) __mempcpy (ccs, cs + 5, endp - (cs + 5))) = &amp;#39;\0&amp;#39;;
			strip (ccs, ccs);
			
			if (__wcsmbs_named_conv (&amp;amp;fcts, ccs[2] == &amp;#39;\0&amp;#39;
			? upstr (ccs, cs + 5) : ccs) != 0)
			{
				/* Something went wrong, we cannot load the conversion modules.
				This means we cannot proceed since the user explicitly asked
				for these. */
				(void) _IO_file_close_it (fp);
				free (ccs);
				__set_errno (EINVAL);
				return NULL;
			}
			
			free (ccs);
			assert (fcts.towc_nsteps == 1);
			assert (fcts.tomb_nsteps == 1);
			
			fp-&amp;gt;_wide_data-&amp;gt;_IO_read_ptr = fp-&amp;gt;_wide_data-&amp;gt;_IO_read_end;
			fp-&amp;gt;_wide_data-&amp;gt;_IO_write_ptr = fp-&amp;gt;_wide_data-&amp;gt;_IO_write_base;
			
			/* Clear the state. We start all over again. */
			memset (&amp;amp;fp-&amp;gt;_wide_data-&amp;gt;_IO_state, &amp;#39;\0&amp;#39;, sizeof (__mbstate_t));
			memset (&amp;amp;fp-&amp;gt;_wide_data-&amp;gt;_IO_last_state, &amp;#39;\0&amp;#39;, sizeof (__mbstate_t));
			
			cc = fp-&amp;gt;_codecvt = &amp;amp;fp-&amp;gt;_wide_data-&amp;gt;_codecvt;
			cc-&amp;gt;__cd_in.step = fcts.towc;
			
			cc-&amp;gt;__cd_in.step_data.__invocation_counter = 0;
			cc-&amp;gt;__cd_in.step_data.__internal_use = 1;
			cc-&amp;gt;__cd_in.step_data.__flags = __GCONV_IS_LAST;
			cc-&amp;gt;__cd_in.step_data.__statep = &amp;amp;result-&amp;gt;_wide_data-&amp;gt;_IO_state;
			
			cc-&amp;gt;__cd_out.step = fcts.tomb;
			
			cc-&amp;gt;__cd_out.step_data.__invocation_counter = 0;
			cc-&amp;gt;__cd_out.step_data.__internal_use = 1;
			cc-&amp;gt;__cd_out.step_data.__flags = __GCONV_IS_LAST | __GCONV_TRANSLIT;
			cc-&amp;gt;__cd_out.step_data.__statep = &amp;amp;result-&amp;gt;_wide_data-&amp;gt;_IO_state;
			
			/* From now on use the wide character callback functions. */
			_IO_JUMPS_FILE_plus (fp) = fp-&amp;gt;_wide_data-&amp;gt;_wide_vtable;
			
			/* Set the mode now. */
			result-&amp;gt;_mode = 1;
		}
	
	}
	return result;
}
libc_hidden_ver (_IO_new_file_fopen, _IO_file_fopen)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fopen/Pasted%20image%2020260403212303.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;ol start=&quot;10&quot;&gt;
&lt;li&gt;_IO_file_fopen 进入 _IO_file_open&lt;br&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fopen/Pasted%20image%2020260403213452.png&quot; alt=&quot;alt text&quot;&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;FILE *
_IO_file_open (FILE *fp, const char *filename, int posix_mode, int prot,
int read_write, int is32not64)
{
	int fdesc;
	if (__glibc_unlikely (fp-&amp;gt;_flags2 &amp;amp; _IO_FLAGS2_NOTCANCEL))
		fdesc = __open_nocancel (filename,
		posix_mode | (is32not64 ? 0 : O_LARGEFILE), prot);
	else
		fdesc = __open (filename, posix_mode | (is32not64 ? 0 : O_LARGEFILE), prot);
	if (fdesc &amp;lt; 0)
		return NULL;
	fp-&amp;gt;_fileno = fdesc;     /*文件标识符fd保存在此*/
	_IO_mask_flags (fp, read_write,_IO_NO_READS+_IO_NO_WRITES+_IO_IS_APPENDING);
	/* For append mode, send the file offset to the end of the file. Don&amp;#39;t
	update the offset cache though, since the file handle is not active. */
	if ((read_write &amp;amp; (_IO_IS_APPENDING | _IO_NO_READS))
	== (_IO_IS_APPENDING | _IO_NO_READS))
	{
		off64_t new_pos = _IO_SYSSEEK (fp, 0, _IO_seek_end);
		if (new_pos == _IO_pos_BAD &amp;amp;&amp;amp; errno != ESPIPE)
		{
			__close_nocancel (fdesc);
			return NULL;
		}
	}
	_IO_link_in ((struct _IO_FILE_plus *) fp);
	return fp;
}
libc_hidden_def (_IO_file_open)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define _IO_mask_flags(fp, f, mask) \
       ((fp)-&amp;gt;_flags = ((fp)-&amp;gt;_flags &amp;amp; ~(mask)) | ((f) &amp;amp; (mask)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;等价于&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;fp-&amp;gt;_flags = (fp-&amp;gt;_flags &amp;amp; ~(_IO_NO_READS|_IO_NO_WRITES|_IO_IS_APPENDING))
           | (read_write &amp;amp; (_IO_NO_READS|_IO_NO_WRITES|_IO_IS_APPENDING));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实际效果&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模式&lt;/th&gt;
&lt;th&gt;&lt;code&gt;read_write&lt;/code&gt; 值&lt;/th&gt;
&lt;th&gt;结果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&amp;quot;r&amp;quot;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_NO_WRITES&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;可读，不可写&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;quot;w&amp;quot;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_NO_READS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;不可读，可写&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;quot;a&amp;quot;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_NO_READS|_IO_IS_APPENDING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;不可读，可写，追加&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;quot;r+&amp;quot;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;可读可写&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;quot;w+&amp;quot;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;可读可写&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;quot;a+&amp;quot;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_IO_IS_APPENDING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;可读可写，追加&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11._IO_file_open进入open64&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;内部调用了openat
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/io/fopen/Pasted%20image%2020260403213603.png&quot; alt=&quot;alt text&quot;&gt;
12. 返回._IO_file_open
13. _IO_file_open进入 _IO_link_in,再次确保FILE对象已经链入_IO_list_all
14. _IO_link_in返回
15.  _IO_file_open返回__fopen_internal 
16. 如果成功直接返回，如果失败fp脱离_IO_list_all，并free掉
17. fopen执行完成&lt;/p&gt;
 &lt;blockquote&gt;This rendering was automatically generated by Frosti Feed and may have formatting issues. For the best experience, please visit: &lt;a href=&quot;https://c4e-i-um-github-io.vercel.app/blog/fopen%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/&quot;&gt;https://c4e-i-um-github-io.vercel.app/blog/fopen%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/&lt;/a&gt;&lt;/blockquote&gt;</content:encoded><dc:creator>Caelum</dc:creator><pubDate>Sat, 04 Apr 2026 00:00:00 GMT</pubDate></item><item><title>archlinux启动流程</title><link>https://c4e-i-um-github-io.vercel.app/blog/archlinux%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B/</link><guid isPermaLink="true">https://c4e-i-um-github-io.vercel.app/blog/archlinux%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B/</guid><description>archlinux启动流程</description><content:encoded>&lt;p&gt;本文大部分引自archlinux的中文wiki&lt;/p&gt;
&lt;p&gt;大致流程可以总结为
固件  -&amp;gt; 引导加载程序  -&amp;gt; 内核 -&amp;gt; initramfs -&amp;gt;早期用户空间 -&amp;gt; 晚期用户空间&lt;/p&gt;
&lt;h1&gt;固件&lt;/h1&gt;
&lt;p&gt;下面引用自维基百科&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E5%9B%BA%E4%BB%B6&quot; title=&quot;zhwp:固件&quot;&gt;固件&lt;/a&gt;是开机时最先执行的程序。
&lt;strong&gt;固件&lt;/strong&gt;（英语：firmware），是一种嵌入在&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E7%A1%AC%E9%AB%94&quot; title=&quot;硬件&quot;&gt;硬件&lt;/a&gt;设备中的&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E8%BB%9F%E9%AB%94&quot; title=&quot;软件&quot;&gt;软件&lt;/a&gt;。通常它是位于&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E7%89%B9%E6%AE%8A%E6%87%89%E7%94%A8%E7%A9%8D%E9%AB%94%E9%9B%BB%E8%B7%AF&quot; title=&quot;特殊应用集成电路&quot;&gt;特殊应用集成电路&lt;/a&gt;（ASIC）或&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E5%8F%AF%E7%A8%8B%E5%BC%8F%E9%82%8F%E8%BC%AF%E8%A3%9D%E7%BD%AE&quot; title=&quot;可编程逻辑器件&quot;&gt;可编程逻辑器件&lt;/a&gt;（PLD）之中的&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E5%BF%AB%E9%96%83%E8%A8%98%E6%86%B6%E9%AB%94&quot; title=&quot;闪存&quot;&gt;闪存&lt;/a&gt;或&lt;a href=&quot;https://zh.wikipedia.org/wiki/EEPROM&quot; title=&quot;EEPROM&quot;&gt;EEPROM&lt;/a&gt;或&lt;a href=&quot;https://zh.wikipedia.org/wiki/PROM&quot; title=&quot;PROM&quot;&gt;PROM&lt;/a&gt;里，有的可以让用户更新。可以应用在非常广泛的电子产品中，从&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E9%81%A5%E6%8E%A7%E5%99%A8&quot; title=&quot;遥控器&quot;&gt;遥控器&lt;/a&gt;、&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E8%AE%A1%E7%AE%97%E5%99%A8&quot; title=&quot;计算器&quot;&gt;计算器&lt;/a&gt;到&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E7%94%B5%E8%84%91&quot; title=&quot;电脑&quot;&gt;电脑&lt;/a&gt;中的&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E9%94%AE%E7%9B%98&quot; title=&quot;键盘&quot;&gt;键盘&lt;/a&gt;、&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E7%A1%AC%E7%9B%98&quot; title=&quot;硬盘&quot;&gt;硬盘&lt;/a&gt;，甚至&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E5%B7%A5%E4%B8%9A%E6%9C%BA%E5%99%A8%E4%BA%BA&quot; title=&quot;工业机器人&quot;&gt;工业机器人&lt;/a&gt;中都可见到它的身影。&lt;/p&gt;
&lt;p&gt;顾名思义，固件是介于软件和硬件之间的。像软件一样，它是由电脑所执行的程序。然而它是对于硬件内部而言更加贴近以及更加重要的部分，而对于外在世界而言较无重要的意义。&lt;/p&gt;
&lt;p&gt;我们这里主要介绍两种：UEFI和BIOS&lt;/p&gt;
&lt;h2&gt;UEFI&lt;/h2&gt;
&lt;p&gt;统一可扩展固件接口（Unified Extensible Firmware Interface，简称 UEFI）**是操作系统和固件之间的接口。UEFI 提供了启动操作系统或运行预启动程序的标准环境。&lt;/p&gt;
&lt;h2&gt;BIOS&lt;/h2&gt;
&lt;p&gt;基本IO系统，（Basic Input-Output System）大多数情况下储存在主板自身的一块闪存内，独立于其它系统存储。&lt;/p&gt;
&lt;h2&gt;UEFI和BIOS&lt;/h2&gt;
&lt;p&gt;UEFI是BIOS的现代替代品，BIOS则较为传统&lt;/p&gt;
&lt;h3&gt;区别&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;BIOS&lt;/th&gt;
&lt;th&gt;UEFI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;程序模式&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;16位实模式&lt;/td&gt;
&lt;td&gt;32/64位保护模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;用户界面&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;文本菜单，键盘操作&lt;/td&gt;
&lt;td&gt;图形界面，支持鼠标和触摸&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;硬盘分区&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;MBR&lt;/strong&gt;（最大2TB，4主分区）&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;GPT&lt;/strong&gt;（容量极大，分区数多）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;启动流程&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;从MBR的固定扇区读取代码&lt;/td&gt;
&lt;td&gt;从ESP分区中的&lt;strong&gt;可执行文件&lt;/strong&gt;启动&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;启动速度&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;相对较慢&lt;/td&gt;
&lt;td&gt;通常更快（支持快速启动）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;安全功能&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;无或很弱&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;安全启动&lt;/strong&gt;，防止恶意软件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;扩展性&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;差&lt;/td&gt;
&lt;td&gt;好，模块化设计&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;最大硬盘&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2TB&lt;/td&gt;
&lt;td&gt;理论18EB（当前受OS限制）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;共同点&lt;/h3&gt;
&lt;p&gt;核心作用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;开机自检&lt;/strong&gt;： 检查CPU、内存、硬盘、显卡等关键硬件是否正常。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;初始化硬件&lt;/strong&gt;： 加载硬件的基本驱动程序。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;引导操作系统&lt;/strong&gt;： 按照预设顺序（如硬盘、U盘、光盘）寻找可启动设备，并加载该设备上&lt;strong&gt;主引导记录&lt;/strong&gt;（MBR）中的引导程序，从而启动操作系统（如Windows、Linux）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;工作流程&lt;/h2&gt;
&lt;p&gt;以bios为例
在电脑开启之后，bios会直接存在于内存之中，然后检测接入电脑的各种输入输出设备，比如U盘，硬盘，显示器，键盘，显卡等，然后会进行加电自检，检查计算机设备硬件是否存在问题，进而保证计算机的正常运行。&lt;/p&gt;
&lt;p&gt;接下来就要分两种情况了
首先要补充一点知识&lt;/p&gt;
&lt;h4&gt;EFI系统分区&lt;/h4&gt;
&lt;p&gt;EFI系统分区（也称为 ESP）是一个与操作系统无关的分区，其中存储了由 UEFI 固件启动的 UEFI 引导加载器、应用程序和驱动，是 UEFI 启动所必须的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/archlinux/archlinux1/Pasted%20image%2020260129103045.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;p&gt;可见我的磁盘标签是GPT并且具有EFI分区，我的电脑是使用的UEFI,绝大多数现代电脑也都用的UEFI&lt;/p&gt;
&lt;h3&gt;使用UEFI的情况&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;加电自检后，UEFI 初始化引导所需的硬件（硬盘、键盘控制器等等）。&lt;/li&gt;
&lt;li&gt;固件读取 NVRAM 中的引导项，以决定要启动哪一个 EFI 应用程序，以及从哪启动（比如从哪一个硬盘和分区）。&lt;ul&gt;
&lt;li&gt;一个引导项可能对应的只是一块硬盘。在这种情况下，固件会寻找硬盘上的 EFI 系统分区，并尝试在后备引导路径 &lt;code&gt;\EFI\BOOT\BOOTx64.EFI&lt;/code&gt; 处（在 IA32（32 位）UEFI 的系统上为 &lt;code&gt;BOOTIA32.EFI&lt;/code&gt;）查找 EFI 应用程序。这就是UEFI 可引导可移除介质的工作原理。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;固件启动 EFI 应用程序。&lt;ul&gt;
&lt;li&gt;这可以是一个引导加载程序，或者是使用 EFISTUB 的 Arch 内核本体。&lt;/li&gt;
&lt;li&gt;还可以是一些其他的 EFI 应用程序，比如 UEFI shell 或引导管理器（例如 systemd-boot) 或 rEFInd）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果启用了安全启动，启动过程将会通过签名验证 EFI 二进制文件的真实性。&lt;/p&gt;
&lt;h2&gt;使用BIOS的情况&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;上电自检后，BIOS 初始化引导所需的硬件（硬盘、键盘控制器等等）。&lt;/li&gt;
&lt;li&gt;BIOS 启动在“BIOS 硬盘顺序”中第一块硬盘上的前 440 字节代码(即主引导记录引导代码区域)&lt;/li&gt;
&lt;li&gt;引导加载程序在 MBR 引导代码的第一阶段，之后会从下列任意一处启动第二阶段代码（如果有的话）：&lt;ul&gt;
&lt;li&gt;MBR 之后的下一个磁盘扇区，即所谓 MBR 后间隙（post-MBR gap，仅在 MBR 分区表上有）。&lt;/li&gt;
&lt;li&gt;分区或者无分区磁盘的卷引导记录（Volume Boot Record，VBR）。&lt;/li&gt;
&lt;li&gt;GRUB 特定 BIOS 引导分区（仅限 GPT 分区硬盘上的 GRUB，用于 GPT 上没有 MBR 后间隙的情况）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;真正的引导加载程序启动。&lt;/li&gt;
&lt;li&gt;随后，引导加载程序通过链式加载或直接加载操作系统内核的方式加载操作系统。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;引导加载程序&lt;/h1&gt;
&lt;p&gt;这是前文BIOS和UEFI启动的程序&lt;/p&gt;
&lt;p&gt;引导加载程序(boot loader)，负责用指定的内核参数加载内核和其他initramfs映像&lt;/p&gt;
&lt;p&gt;引导管理器(boot managerc),让用户使用启动选项菜单或其他方式控制启动过程&lt;/p&gt;
&lt;p&gt;一些程序例如GRUB兼具上面两者的功能&lt;/p&gt;
&lt;p&gt;在 UEFI 的情况下，内核本身可以由 UEFI 使用 EFI boot stub接启动。要在引导前编辑内核参数，可以使用引导管理器或是单独的引导加载程序。&lt;/p&gt;
&lt;h3&gt;注意&lt;/h3&gt;
&lt;p&gt;引导加载程序必须能够访问通常位于 &lt;code&gt;/boot&lt;/code&gt; 目录下的内核和 initramfs 映像才能成功引导 Arch 系统。也就是说，引导加载程序必须解决从块设备、堆叠块设备（LVM、RAID、dm-crypt、LUKS 等）开始，到内核和 initramfs 映像所在文件系统为止的访问。
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/archlinux/archlinux1/Pasted%20image%2020260129104748.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;p&gt;因为几乎没有引导加载程序支持堆叠块设备，并且文件系统引入的一些新特性可能尚未有任何引导加载程序支持，所以用广泛支持的文件系统（例如 FAT32）单独创建 /boot 分区通常更可行。&lt;/p&gt;
&lt;h1&gt;内核&lt;/h1&gt;
&lt;p&gt;然后就到了下一步内核
boot loader会启动包含内核的vmlinux映像&lt;/p&gt;
&lt;p&gt;内核是操作系统的核心。它运行于一个叫_内核空间_的底层上，负责机器硬件和应用程序之间的交流。在继续进入用户空间前，内核会首先执行硬件枚举和初始化。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在linux系统中，&lt;strong&gt;vmlinux&lt;/strong&gt;（&lt;strong&gt;vmlinuz&lt;/strong&gt;）是一个包含linux kernel的静态链接的可执行文件，文件类型可能是linux接受的可执行文件格式之一（&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E5%8F%AF%E5%9F%B7%E8%A1%8C%E8%88%87%E5%8F%AF%E9%8F%88%E6%8E%A5%E6%A0%BC%E5%BC%8F&quot; title=&quot;可执行与可链接格式&quot;&gt;ELF&lt;/a&gt;、&lt;a href=&quot;https://zh.wikipedia.org/wiki/COFF&quot; title=&quot;COFF&quot;&gt;COFF&lt;/a&gt;或&lt;a href=&quot;https://zh.wikipedia.org/wiki/A.out&quot; title=&quot;A.out&quot;&gt;a.out&lt;/a&gt;），vmlinux若要用于调试时则必须要在开机前增加symbol table。
随着 linux Kernel 的成长，核心的内容日益增加超越了原本的限制大小。bzImage (big zImage) 格式则为了克服此缺点开始发展，利用将核心切割成不连续的存储器区块来克服大小限制。
bzImage 格式仍然是以 zlib 算法来做压缩，虽然有一些广泛的误解就是因为以 bz- 为开头，而让人误以为是使用 bzip2 压缩方式（bzip2 包所带的工具程序通常是以 bz- 为开头的，例如 bzless, bzcat ...）。
bzImage 文件是一个特殊的格式，包含了 bootsect.o + setup.o + misc.o + piggy.o 串接。piggy.o 包含了一个 gzip 格式的 vmlinux 文件（可以参看 arch/i386/boot／下的 compressed/Makefile piggy.o）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;initramfs&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;initramfs&lt;/em&gt;（初始内存文件系统，init ial RAM file system）映像是一个 cpio存档文件，为早期用户空间（见下文）启动晚期用户空间提供了必要的文件。这包括了所有用于定位，访问和挂载根文件系统的内核模块、用户空间工具、相关库文件、类似 udev 规则的支持文件等。得益于 initramfs 的概念，它可以处理更加复杂的配置场景，例如从外置硬盘启动，堆叠设备（例如逻辑卷，软 RAID，压缩和加密），或是在早期用户空间中运行一个微型 SSH 服务器，以供远程解锁或为根文件系统执行维护任务。&lt;/p&gt;
&lt;p&gt;绝大部分内核模块都将在初始化流程的后期阶段，由udev在根切换到根文件系统后加载。&lt;/p&gt;
&lt;p&gt;具体流程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;/&lt;/code&gt;下的根文件系统原本是一个空的 rootfs，它是一个特殊的 tmpfs 或 ramfs 实例。这里就是 initramfs 会解压到的临时根文件系统。&lt;/li&gt;
&lt;li&gt;内核会将其内置 initramfs 解压到临时根文件系统下。Arch Linux 官方支持的内核使用空白存档作为内置 initramfs，即构建内核时的默认行为。&lt;/li&gt;
&lt;li&gt;然后，内核会按照引导加载器传递的命令行参数指定的顺序解压外置 initramfs 映像，覆盖掉之前内置 initramfs 或其它解压出来的文件。注意，可以将多个 initramfs 映像合并为一个文件，内核会按照文件内的顺序加载映像。&lt;ul&gt;
&lt;li&gt;如果首个 initramfs 映像未经压缩，那么内核会在解包该映像后在 &lt;code&gt;/kernel/x86/microcode/&lt;/code&gt; 目录查找 CPU 微码更新，在 &lt;code&gt;/kernel/firmware/acpi/&lt;/code&gt; 目录查找 ACPI 表更新。&lt;/li&gt;
&lt;li&gt;在适用的情况下，在处理完 CPU 微码和 ACPI 表更新后，内核会继续解压剩余的 initramfs 映像。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;initramfs 映像是 Arch Linux 推荐的早期用户空间配置方法，并可通过 mkinitcpio，dracut 或 booster来生成。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;内置 initramfs 是内核镜像里的 “附属品”，体积只有几十 KB，仅包含最基础的内核启动代码，没有任何硬件驱动；&lt;/li&gt;
&lt;li&gt;外置 initramfs 是独立文件，体积几十 MB，包含了你在 &lt;code&gt;mkinitcpio.conf&lt;/code&gt; 中配置的所有模块、钩子、脚本，是系统启动真正依赖的早期用户空间。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;内核保留内置迷你 initramfs，核心是为了&lt;strong&gt;兼容性兜底&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果系统没有外置 initramfs（比如极简内核、嵌入式系统），内置 initramfs 能保证内核至少能启动到 “紧急 shell”；&lt;/li&gt;
&lt;li&gt;对于桌面 / 服务器系统（如 Arch），外置 initramfs 可灵活定制（加驱动、加加密脚本），无需重新编译内核 —— 这也是 mkinitcpio 的核心价值：用户不用改内核，只需定制外置 initramfs 就能适配不同硬件。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;udev 是一个用户空间的设备管理器，用于为事件设置处理程序。作为守护进程， udev 接收的事件主要由 linux 内核生成，这些事件是外部设备产生的物理事件。总之， udev 探测外设和热插拔，将设备控制权传递给内核，例如加载内核模块或设备固件。&lt;/p&gt;
&lt;p&gt;udev是一个用户空间系统，可以让操作系统管理员为事件注册用户空间处理器。为了实现外设侦测和热插拔，&lt;em&gt;udev&lt;/em&gt; 守护进程接收 Linux 内核发出的外设相关事件; 加载内核模块、设备固件; 调整设备权限，让普通用户和用户组能够访问设备。&lt;/p&gt;
&lt;p&gt;Ramfs 是一种极简的文件系统，它将 Linux 的磁盘缓存机制（页缓存与目录项缓存）封装为可动态调整大小的&lt;strong&gt;基于内存的文件系统&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;mkinitcpio&lt;/h3&gt;
&lt;p&gt;外置的initramfs是由mkinitcpio.conf生成的&lt;/p&gt;
&lt;p&gt;Arch Linux 中 &lt;code&gt;/etc/mkinitcpio.conf&lt;/code&gt; 文件里 &lt;code&gt;MODULES&lt;/code&gt; 配置项的含义，以及当前配置的 &lt;code&gt;nvme&lt;/code&gt; 和一系列 &lt;code&gt;nvidia&lt;/code&gt; 相关模块的作用 —— 这是定制 initramfs（早期用户空间）的核心配置，决定了哪些内核模块会被&lt;strong&gt;强制打包进 initramfs&lt;/strong&gt;，并在系统启动最早期（所有启动钩子运行前）加载，是解决硬件驱动早期加载、避免启动故障的关键。&lt;/p&gt;
&lt;h4&gt;MODELES&lt;/h4&gt;
&lt;p&gt;这个数组是手动指定的、需要&lt;strong&gt;强制打包进 initramfs&lt;/strong&gt; 的内核模块，每一个模块都对应核心硬件功能，且都是 “内核自动探测 / 钩子加载可能不及时” 的关键模块：&lt;/p&gt;
&lt;p&gt;这是定制 initramfs（早期用户空间）的核心配置，决定了哪些内核模块会被&lt;strong&gt;强制打包进 initramfs&lt;/strong&gt;，并在系统启动最早期（所有启动钩子运行前）加载，是解决硬件驱动早期加载、避免启动故障的关键。&lt;/p&gt;
&lt;p&gt;我这里主要加载了NVMe 固态硬盘的核心驱动模块以及nvidia驱动的一些模块&lt;/p&gt;
&lt;p&gt;给和我一样的新手提醒：NVIDIA 闭源驱动是&lt;strong&gt;第三方模块&lt;/strong&gt;，内核默认不识别，无法通过 udev 自动探测加载；若不提前打包进 initramfs，而是靠 &lt;code&gt;modules-load.d&lt;/code&gt; 加载，会导致：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;早期用户空间阶段显卡无输出（开机黑屏）；&lt;/li&gt;
&lt;li&gt;切换到真正根文件系统后才加载 NVIDIA 驱动，出现显示闪烁、分辨率异常；&lt;/li&gt;
&lt;li&gt;启用 KMS 早启动（Arch 推荐配置）时，必须在 initramfs 阶段加载 &lt;code&gt;nvidia_drm&lt;/code&gt;，否则显卡驱动无法正常初始化。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因为靠 &lt;code&gt;modules-load.d&lt;/code&gt;加载时在早期用户空间阶段，而在那时，你还没加载驱动
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/archlinux/archlinux1/Pasted%20image%2020260129211504.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h4&gt;BINARIES&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;BINARIES&lt;/code&gt; 用于将你需要的&lt;strong&gt;额外二进制可执行文件&lt;/strong&gt;（比如命令行工具、自定义程序）加入到 CPIO 格式的 initramfs 镜像中（initramfs 本质是 cpio 压缩包，&lt;code&gt;mkinitcpio&lt;/code&gt; 就是 “make init cpio” 的缩写）；
比如你想在 initramfs 阶段（启动时的紧急 shell）执行 &lt;code&gt;lsblk&lt;/code&gt; 查看磁盘分区（默认 initramfs 没有 &lt;code&gt;lsblk&lt;/code&gt;），就需要添加：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;BINARIES=(/usr/bin/lsblk)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你想让 initramfs 自动读取密钥文件解密根分区（无需开机手动输密码），可把密钥文件添加到 &lt;code&gt;FILES&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# 示例：添加LUKS解密密钥文件到initramfs
FILES=(/etc/cryptkey.bin)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;FILES&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;FILES&lt;/code&gt; 和 &lt;code&gt;BINARIES&lt;/code&gt; 作用类似（都是向 initramfs 中添加文件），但处理方式完全不同；
对于files来说会&lt;code&gt;as-is&lt;/code&gt;（原样添加）—— mkinitcpio 会把你指定的文件&lt;strong&gt;原封不动&lt;/strong&gt;复制到 initramfs 中而Binaries不是&lt;/p&gt;
&lt;h4&gt;HOOKS&lt;/h4&gt;
&lt;p&gt;HOOKS 是整个配置文件中最重要的项
既控制 “哪些模块 / 脚本被打包进 initramfs”，也控制 “启动时按什么顺序执行什么操作”；
&lt;strong&gt;顺序极其重要&lt;/strong&gt;（后一个钩子依赖前一个的执行结果），一般不要乱改顺序；
&lt;strong&gt;必需钩子&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;base&lt;/code&gt;：必选（除非你完全清楚自己在做什么），包含 initramfs 运行的最基础脚本 / 模块（比如 shell、基础工具）；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;udev&lt;/code&gt;/&lt;code&gt;systemd&lt;/code&gt;：二选一必选（自动加载模块的核心），我用的是 &lt;code&gt;systemd&lt;/code&gt; 替代了传统的 &lt;code&gt;udev&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filesystems&lt;/code&gt;：必选（除非你在 MODULES 里手动指定了所有文件系统模块），负责加载 ext4/xfs/btrfs 等文件系统驱动；
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/archlinux/archlinux1/Pasted%20image%2020260129213441.png&quot; alt=&quot;alt text&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;COMPRESSION_OPTIONS&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;COMPRESSION&lt;/code&gt;：设置 initramfs 镜像的压缩算法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心作用&lt;/strong&gt;：指定 mkinitcpio 生成 initramfs 镜像时使用的&lt;strong&gt;压缩算法&lt;/strong&gt;（initramfs 本质是 cpio 包 + 压缩层，最终生成 &lt;code&gt;initramfs-linux.img&lt;/code&gt; 是 “cpio + 压缩” 的组合）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;默认行为&lt;/strong&gt;：mkinitcpio 会自动适配内核版本 ——Linux 内核 ≥5.9 用 &lt;code&gt;zstd&lt;/code&gt;（Arch 主流内核都满足），&amp;lt;5.9 用 &lt;code&gt;gzip&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特殊值&lt;/strong&gt;：若设为 &lt;code&gt;COMPRESSION=&amp;quot;cat&amp;quot;&lt;/code&gt;，则生成&lt;strong&gt;未压缩&lt;/strong&gt;的 initramfs 镜像（体积最大，但解压最快）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;MODULES_DECOMPRESS&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心作用&lt;/strong&gt;：开关（&lt;code&gt;yes&lt;/code&gt;/&lt;code&gt;no&lt;/code&gt;），控制 mkinitcpio 生成 initramfs 时，是否先&lt;strong&gt;解压内核模块（.ko）和固件文件&lt;/strong&gt;，再打包进镜像；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;默认行为&lt;/strong&gt;：&lt;code&gt;no&lt;/code&gt; → 内核模块 / 固件保持&lt;strong&gt;原始压缩状态&lt;/strong&gt;（Linux 内核模块默认是 xz/gzip 压缩的），直接打包进 initramfs；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开启（yes）的目的&lt;/strong&gt;：配合「高压缩参数」（如 xz -9e、zstd -22）进一步减小 initramfs 体积 —— 因为模块先解压再用指定算法压缩，比 “模块自带压缩 + initramfs 压缩” 的「双重压缩」效率更高；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开启（yes）的代价&lt;/strong&gt;：早期启动阶段（initramfs 解压后），模块会以&lt;strong&gt;未压缩状态&lt;/strong&gt;加载到内存，占用更多 RAM；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关键注意&lt;/strong&gt;：开启后，解压后的模块会放在 initramfs 的 “未压缩早期 CPIO” 中，避免双重压缩（否则会抵消高压缩的收益）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;早期用户空间&lt;/h1&gt;
&lt;p&gt;Linux 内核启动后，&lt;strong&gt;本身无法直接识别所有硬件和根文件系统&lt;/strong&gt;（比如加密分区、LVM 逻辑卷、NVMe 固态硬盘驱动、RAID 控制器等），如果直接尝试挂载根文件系统，大概率会 “找不到设备” 或 “无法解析文件系统”。&lt;/p&gt;
&lt;p&gt;因此，内核会先加载一个&lt;strong&gt;精简的、内存中的临时文件系统（initramfs/initrd）&lt;/strong&gt; —— 这就是 “早期用户空间”：它是一个迷你版的用户空间环境，包含了启动真正根文件系统所需的最小化工具、驱动和脚本，核心使命是 “帮内核扫清障碍，让内核能成功挂载并切换到真正的根文件系统”。&lt;/p&gt;
&lt;p&gt;简单类比：早期用户空间就像 “系统启动的前置助手”，先帮内核搞定硬件识别、加密解密、存储栈组装这些 “前置工作”，再把控制权交还给真正的根文件系统。&lt;/p&gt;
&lt;p&gt;早期用户空间阶段（亦称“initramfs 阶段”）在由 initramfs映像提供文件的 rootfs 中进行，始于内核以 PID 1 执行 &lt;code&gt;/init&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/init&lt;/code&gt;程序我的是systemd&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;加载内核模块（systemd-modules-load (8)）&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：加载挂载真正根文件系统必需的内核模块（驱动）。&lt;/p&gt;
&lt;p&gt;  内核本身只内置了最基础的驱动，像 NVMe 硬盘、SATA 控制器、USB 存储、加密分区（dm-crypt）、LVM（dm-mod）等驱动，都以 “模块” 形式存在，需要手动加载。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;实现&lt;/strong&gt;：基于 systemd 的 initramfs 会通过 &lt;code&gt;systemd-modules-load.service&lt;/code&gt;，读取 &lt;code&gt;/etc/modules-load.d/&lt;/code&gt;、&lt;code&gt;/usr/lib/modules-load.d/&lt;/code&gt; 等配置文件，自动加载指定模块；如果是 BusyBox 版 initramfs，则通过 &lt;code&gt;modprobe&lt;/code&gt;/&lt;code&gt;insmod&lt;/code&gt; 命令手动加载。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;举例（Arch 场景）&lt;/strong&gt;：如果你的根分区在 NVMe 硬盘上，必须加载 &lt;code&gt;nvme&lt;/code&gt; 模块；如果用了 LUKS 加密，必须加载 &lt;code&gt;dm-crypt&lt;/code&gt; 模块 —— 少了这些，内核找不到根硬盘，直接卡在 “Waiting for root device”。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;构建存储栈 + 解密根文件系统&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这是早期用户空间最复杂也最核心的工作（尤其对加密 / RAID/LVM 系统）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;“存储栈” 是什么&lt;/strong&gt;：把底层硬件→逻辑卷 / RAID→加密层→文件系统 这一系列组件组装起来，形成能访问根分区的完整链路。&lt;/p&gt;
&lt;p&gt;  比如：&lt;code&gt;NVMe 硬盘（/dev/nvme0n1p3）&lt;/code&gt; → &lt;code&gt;LUKS 加密层（dm-crypt）&lt;/code&gt; → &lt;code&gt;LVM 逻辑卷（vg0/root）&lt;/code&gt; → &lt;code&gt;ext4 文件系统&lt;/code&gt;，这个 “栈” 必须在早期用户空间组装完成。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;核心工具&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dm-crypt&lt;/code&gt;：解密 LUKS 加密的根分区（Arch 中加密根分区时，initramfs 会弹出密码输入界面，输入后解密）；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dm-verity&lt;/code&gt;：验证根文件系统的完整性（防止篡改）；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mdadm&lt;/code&gt;：组装软 RAID 阵列；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LVM（lvm2）&lt;/code&gt;：激活逻辑卷组 / 逻辑卷；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;systemd-repart&lt;/code&gt;：动态调整分区大小（比如启动时自动扩容根分区）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;关键注意点&lt;/strong&gt;：如果根分区加密，解密操作&lt;strong&gt;只能在早期用户空间做&lt;/strong&gt;—— 因为解密前根文件系统完全不可访问，没有任何工具能运行。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;udev 解析块设备持久化名称&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;问题背景&lt;/strong&gt;：Linux 设备名（如 &lt;code&gt;/dev/sda3&lt;/code&gt;）是动态的（比如插了 U 盘后，硬盘可能从 sda 变成 sdb），直接用动态名找根分区会出错。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;udev 的作用&lt;/strong&gt;：在早期用户空间中，udev 会扫描硬件，将动态设备名映射为&lt;strong&gt;持久化名称&lt;/strong&gt;（比如 &lt;code&gt;/dev/disk/by-uuid/xxxx&lt;/code&gt;、&lt;code&gt;/dev/mapper/cryptroot&lt;/code&gt;），确保内核能精准找到根分区，不会因设备名变动导致启动失败。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;加载 DRM 模块（早启动 KMS）&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DRM（Direct Rendering Manager）&lt;/strong&gt;：Linux 显卡驱动的核心框架；KMS（Kernel Mode Setting）：内核模式设置，负责显卡分辨率、显示输出。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;为什么早加载&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;支持启动时的图形化启动画面（比如 Arch 的 plymouth 美化启动界面）；&lt;/li&gt;
&lt;li&gt;内核能更早输出显卡相关日志，方便排查启动故障；&lt;/li&gt;
&lt;li&gt;避免后续切换根文件系统时出现显示异常（比如黑屏、分辨率错误）。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;其他关键任务（挂载根前必做）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt; &lt;code&gt;fsck&lt;/code&gt; 和 “从休眠中恢复” 是早期用户空间的额外核心任务，且&lt;strong&gt;只能在挂载真正根文件系统前执行&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fsck&lt;/code&gt;：文件系统检查。如果根文件系统有损坏，&lt;code&gt;fsck&lt;/code&gt; 必须在 “未挂载” 状态下执行（挂载后执行会破坏文件系统），因此只能放在早期用户空间；&lt;/li&gt;
&lt;li&gt;休眠恢复：从交换分区 / 休眠镜像恢复系统时，需要先挂载休眠镜像所在的分区，且此时不能挂载根文件系统（否则会冲突），因此也必须在早期用户空间完成。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;systemd&lt;/h2&gt;
&lt;p&gt;systemd 是&lt;strong&gt;Linux 系统的系统和服务管理器&lt;/strong&gt;，也是现代绝大多数 Linux 发行版（包括你使用的 Arch Linux）默认的&lt;strong&gt;PID 1 进程&lt;/strong&gt;（系统启动后用户空间运行的第一个进程），核心替代了传统的 SysVinit、Upstart 等初始化系统，负责&lt;strong&gt;接管系统启动、管理服务生命周期、统筹系统各类资源&lt;/strong&gt;，是 Linux 系统运行的核心管家。&lt;/p&gt;
&lt;p&gt;简单说：系统开机后，内核完成初始化后，第一个启动的用户空间程序就是 systemd，之后所有的系统服务、应用进程，几乎都是由 systemd 启动 / 管理的，它也会全程监控这些进程，同时处理系统关机、休眠、设备挂载等核心操作。&lt;/p&gt;
&lt;h2&gt;modules-load.d&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;modules-load.d&lt;/code&gt; 是 &lt;strong&gt;systemd 系 Linux 系统的标准化配置目录&lt;/strong&gt;，专门用于定义 “系统启动时需要&lt;strong&gt;自动加载的内核模块&lt;/strong&gt;”。&lt;/p&gt;
&lt;p&gt;我这里是开机自动加载tun内核模块，里面一般写的是虚拟驱动模块，因为一般的硬件驱动模块（NVMe、SATA、USB 存储）被内核通过 udev 自动探测加载，无需配 conf，而功能类模块（dm-crypt、LVM/dm-mod）：mkinitcpio 钩子自动打包加载，无需配 conf
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/archlinux/archlinux1/Pasted%20image%2020260129210706.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h1&gt;晚期用户空间&lt;/h1&gt;
&lt;p&gt;晚期用户空间从 init进程开始。Arch 官方支持的 systemd基于单元和服务的概念，但这里描述的功能在很大程度上与其它 init 系统重叠。&lt;/p&gt;
&lt;h3&gt;getty&lt;/h3&gt;
&lt;p&gt;init会为每个虚拟终端（通常有六个）调用一次 getty，它会初始化终端并保护其免受未授权访问。在提供用户名和密码后，&lt;em&gt;getty&lt;/em&gt; 会对照 &lt;code&gt;/etc/passwd&lt;/code&gt; 和 &lt;code&gt;/etc/shadow&lt;/code&gt; 检查是否正确。如果正确，就接着调用 login(1)。&lt;/p&gt;
&lt;h5&gt;/etc/passwd&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;/etc/passwd&lt;/code&gt; 是 &lt;strong&gt;Linux 系统的核心用户账户配置文件&lt;/strong&gt;，存储了系统中 ** 所有用户（包括 root、普通用户、系统服务用户）** 的基础账户信息，是系统识别、验证用户身份的核心依据，&lt;strong&gt;所有用户都拥有只读权限&lt;/strong&gt;，仅 root 可修改，是 Linux 多用户管理的基础文件。&lt;/p&gt;
&lt;p&gt;早期该文件还存储用户的加密密码，后因安全问题（全局可读易被破解），密码被迁移到 &lt;code&gt;/etc/shadow&lt;/code&gt;（仅 root 可读），如今 &lt;code&gt;/etc/passwd&lt;/code&gt; 仅保留&lt;strong&gt;非敏感的基础用户信息&lt;/strong&gt;，这是 Linux 安全设计的重要调整。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/etc/passwd&lt;/code&gt; 中&lt;strong&gt;每行对应一个用户&lt;/strong&gt;，行内用&lt;strong&gt;冒号 &lt;code&gt;:&lt;/code&gt;&lt;/strong&gt; 分隔为&lt;strong&gt;7 个固定字段&lt;/strong&gt;，字段顺序不可乱，空字段也需保留冒号（格式错误会导致用户登录失败）。这里不再展开&lt;/p&gt;
&lt;h4&gt;为什么需要 /etc/shadow？&lt;/h4&gt;
&lt;p&gt;早期 Linux 把用户加密密码直接存在 &lt;code&gt;/etc/passwd&lt;/code&gt; 的第二个字段，但 &lt;code&gt;/etc/passwd&lt;/code&gt; 为了让系统程序识别用户，必须设置&lt;strong&gt;全局可读权限&lt;/strong&gt;（644），这意味着任何用户都能读取加密密码串，再通过暴力破解工具尝试解密，存在极大安全风险。&lt;/p&gt;
&lt;p&gt;为了解决这个问题，Linux 引入了&lt;strong&gt;影子密码机制&lt;/strong&gt;：将&lt;strong&gt;加密密码、密码有效期、账户锁定&lt;/strong&gt;等敏感信息从 &lt;code&gt;/etc/passwd&lt;/code&gt; 迁移到 &lt;code&gt;/etc/shadow&lt;/code&gt;，并将其权限严格限制为&lt;strong&gt;仅 root 可读写（600）&lt;/strong&gt;，而 &lt;code&gt;/etc/passwd&lt;/code&gt; 仅保留非敏感的基础用户信息，实现了&lt;strong&gt;敏感信息与基础信息的分离&lt;/strong&gt;，大幅提升了系统账户安全。&lt;/p&gt;
&lt;p&gt;迁移后，&lt;code&gt;/etc/passwd&lt;/code&gt; 的第二个字段固定为占位符 &lt;code&gt;x&lt;/code&gt;，表示&lt;strong&gt;密码信息已迁移至 /etc/shadow&lt;/strong&gt;。&lt;/p&gt;
&lt;h4&gt;login&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;login&lt;/em&gt; 会根据 &lt;code&gt;/etc/passwd&lt;/code&gt; 设置环境变量并启动用户 shell，从而为用户配置一个会话。在成功登录后，启动登录 shell 前，&lt;em&gt;login&lt;/em&gt; 程序会显示 /etc/motd（message of the day）的内容，你可以用它来显示服务条款以提醒用户你的本地策略，也可以显示其它提示信息。&lt;/p&gt;
&lt;h4&gt;/etc/motd&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;/etc/motd&lt;/code&gt; 是 Linux 系统的&lt;strong&gt;每日提示信息文件&lt;/strong&gt;，全称 &lt;strong&gt;Message of the Day&lt;/strong&gt;，核心作用是&lt;strong&gt;用户通过本地终端 / SSH 远程登录系统后，自动显示的欢迎 / 提示信息&lt;/strong&gt;，是系统管理员发布系统通知、维护提醒、安全警告的常用方式，普通用户也可自定义个性化登录欢迎语，&lt;strong&gt;仅对终端登录生效，图形界面登录不会显示该文件内容&lt;/strong&gt;。&lt;/p&gt;
&lt;h4&gt;shell&lt;/h4&gt;
&lt;p&gt;用户的 shell启动后，在显示命令行提示符前，通常会执行一个运行时配置文件（例如 bashrc。如果用户账户配置为在登录时自动启动 X，那么运行时配置文件会调用 startx 或 xinit，具体内容请参考&lt;a href=&quot;https://wiki.archlinuxcn.org/wiki/Arch_%E7%9A%84%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B#%E5%9B%BE%E5%BD%A2%E4%BC%9A%E8%AF%9D%EF%BC%88Xorg%EF%BC%89&quot;&gt;#图形会话（Xorg）&lt;/a&gt;。&lt;/p&gt;
&lt;h3&gt;显示管理器&lt;/h3&gt;
&lt;p&gt;这里还没提到wayland&lt;/p&gt;
&lt;h4&gt;图形会话（Xorg）&lt;/h4&gt;
&lt;p&gt;xinit 会调用用户的 xinitrc 运行时配置文件，后者一般会启动一个窗口管理器或。如果用户退出了窗口管理器，&lt;em&gt;xinit&lt;/em&gt;、&lt;em&gt;startx&lt;/em&gt;、shell、login 就会依次中断，返回到 &lt;em&gt;getty&lt;/em&gt; 或显示管理器。&lt;/p&gt;
 &lt;blockquote&gt;This rendering was automatically generated by Frosti Feed and may have formatting issues. For the best experience, please visit: &lt;a href=&quot;https://c4e-i-um-github-io.vercel.app/blog/archlinux%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B/&quot;&gt;https://c4e-i-um-github-io.vercel.app/blog/archlinux%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B/&lt;/a&gt;&lt;/blockquote&gt;</content:encoded><dc:creator>Caelum</dc:creator><pubDate>Thu, 29 Jan 2026 00:00:00 GMT</pubDate></item><item><title>Rust引用和借用</title><link>https://c4e-i-um-github-io.vercel.app/blog/rust%E5%BC%95%E7%94%A8%E5%92%8C%E5%80%9F%E7%94%A8/</link><guid isPermaLink="true">https://c4e-i-um-github-io.vercel.app/blog/rust%E5%BC%95%E7%94%A8%E5%92%8C%E5%80%9F%E7%94%A8/</guid><description>Rust引用和借用</description><content:encoded>&lt;h1&gt;概述&lt;/h1&gt;
&lt;h2&gt;函数传递常见问题&lt;/h2&gt;
&lt;p&gt;在上篇文章我们说到，凡是用到Box的变量中赋值的时候会出现所有权的转移，称为移动&lt;/p&gt;
&lt;p&gt;但是每次这样移动未免太过麻烦，并且在函数传递的时候更不方便&lt;/p&gt;
&lt;p&gt;比如下面这个例子&lt;/p&gt;
&lt;p&gt;format!是重新创建一个格式化后的String类型变量&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn main(){
	let m1 = String::from(&amp;quot;Hello&amp;quot;);
	let m2 = Srting::from(&amp;quot;world&amp;quot;);
	greet(m1,m2);   #L2
	let s = format!(&amp;quot;{} {}&amp;quot;,m1,m2);  #L3  //Error: m1 and m2 are moved
}

fn greet(g1: Srting,g2: String){
	println!(&amp;quot;{} {}!&amp;quot;,g1,g2);  #L1
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126173319.png&quot; alt=&quot;alt text&quot;&gt;
这个例子是在函数传递的时候发生了移动，导致原来的变量无法访问，如果访问编译器会报错&lt;/p&gt;
&lt;h2&gt;解决方式&lt;/h2&gt;
&lt;h3&gt;第一种（返回值的方式）&lt;/h3&gt;
&lt;p&gt;let (m1_again,m2_again) = greet(m1,m2);是把greet的两个返回值分别返回给m1_again,m2_again&lt;/p&gt;
&lt;p&gt;把所有权再移动回main函数中的变量
所有权转移： m1 -&amp;gt; g1 -&amp;gt; m1_again
m2 -&amp;gt; g2 -&amp;gt; m2_again&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn main(){
	let m1 = String::from(&amp;quot;Hello&amp;quot;);
	let m2 = String::from(&amp;quot;world&amp;quot;); #L1
	let (m1_again,m2_again) = greet(m1,m2);
	let s = format!(&amp;quot;{} {}&amp;quot;,m1_again,m2_again); #L2
}

fn greet(g1: String,g2: Srting) -&amp;gt; (String, String){
	println!(&amp;quot;{} {}&amp;quot;, g1, g2);
	(g1, g2)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126174033.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h3&gt;第二种（引用的方式）&lt;/h3&gt;
&lt;p&gt;由第一种可以看出，返回值的方式同样复杂，为了解决这种不方便，rust有一种类型叫做引用&lt;/p&gt;
&lt;h1&gt;引用&lt;/h1&gt;
&lt;p&gt;引用：引用是没有 “所有权” 的指针&lt;/p&gt;
&lt;p&gt;也就是使用引用类型的变量不会移动所有权&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn main(){

	let m1 = String::from(&amp;quot;Hello&amp;quot;);
	let m1 = String::from(&amp;quot;world&amp;quot;); #L1
	greet(m1,m2); #L3
	let s = format!(&amp;quot;{} {}&amp;quot;, m1 m2);
}

fn greet(){g1: &amp;amp;Srting,g2: &amp;amp;String}{
	#L2
	println!(&amp;quot;{} {}&amp;quot;, g1, g2);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126174926.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h2&gt;解引用指针以访问数据&lt;/h2&gt;
&lt;p&gt;解引用运算符 ： *&lt;/p&gt;
&lt;p&gt;这是我们常理解的正常解引用，也就是显示解引用&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let mut x: Box&amp;lt;i32&amp;gt; = Box::new(1);
let a: i32 = *x;
*x += 1;

let r1: &amp;amp;Box&amp;lt;i32&amp;gt; = &amp;amp;x;
let b: i32 = **r1;

let r2: &amp;amp;i32 = &amp;amp;*x;
let c: i32 = *r2;  #L1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126180353.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;p&gt;而在Rust中，很多情况都是隐式的解引用&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let x: Box&amp;lt;i32&amp;gt; = Box::new(-1);
let x_abs1 = i32::abs(*x);   //显式解引用
let x_abs2 = x.abs();  //隐式解引用
assert_eq!(x_abs1,x_abs2);

let r: &amp;amp;Box&amp;lt;i32&amp;gt; = &amp;amp;x;
let r_abs1 = i32::abs(**r); //显式解引用,两次
let r_abs2 = r.abs()  //隐式解引用，两次
assert_eq!(r_abs1,r_abs2)

let s = String::from(&amp;quot;Hello&amp;quot;);
let s_len1 = str::len(&amp;amp;s); //显式解引用
let s_len2 = s.len(); //隐式解引用
assert_eq!(s_len1,s_len2)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们不难发现，Rust中有一个规律&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一般   类型::方法(变量)   这种需要显式解引用（或显式引用）&lt;/li&gt;
&lt;li&gt;而      变量.方法()   这种一般是隐式解引用（或隐式引用），并且支持多层&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;引用带来的问题&lt;/h2&gt;
&lt;p&gt;别名：通过不同的变量访问同一数据
别名数据：可被多个变量访问的一块数据&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;一个指针变量，如果把这个变量的值传给另一个指针变量叫创建了他的一个别名（也就是所谓的传递地址）&lt;/li&gt;
&lt;li&gt;一般来说引用一个变量也“相当于”创建了他的一个别名（但Rust中引用与别名不完全相同）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;试想如果我释放了引用数据，然后再通过原变量访问，就出现了UAF&lt;/p&gt;
&lt;p&gt;或者我通过引用改变了Heap内存的值，而原变量却不知道，可能会出现与预期不相符的结果&lt;/p&gt;
&lt;p&gt;或引用和原变量同时修改，可能会导致数据竞争，产生漏洞&lt;/p&gt;
&lt;h2&gt;Rust为解决引用带来的不安全性的方案&lt;/h2&gt;
&lt;p&gt;Rust为解决这些可能产生未定义行为的代码，引入了一个原则：别名和可变性不可以同时存在&lt;/p&gt;
&lt;p&gt;先看下面这个例子&lt;/p&gt;
&lt;p&gt;补充：
Vec的底层结构&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ptr&lt;/code&gt;：指向堆上分配的内存起始地址的指针&lt;/li&gt;
&lt;li&gt;&lt;code&gt;len&lt;/code&gt;：当前已存储的元素数量&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cap&lt;/code&gt;：分配的内存总共能容纳的元素数量
push 的逻辑：&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;len &amp;lt; cap&lt;/code&gt;：直接在&lt;code&gt;ptr + len&lt;/code&gt;的内存位置写入新元素，&lt;code&gt;len += 1&lt;/code&gt;，&lt;strong&gt;指针和内存区域完全不变&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;len &amp;gt;= cap&lt;/code&gt;：触发扩容（reallocation）—— 分配一块更大的内存（通常是原容量的 2 倍，小容量时可能按固定值增长），把原有元素拷贝到新内存，释放旧内存，然后在新内存尾部写入新元素，此时&lt;code&gt;ptr&lt;/code&gt;指向新内存地址。
let mut v： Vec&amp;lt;i32&amp;gt; = vec![1, 2, 3];
是初始创建了包含3个元素1,2,3,容量为3的一个vec&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let mut v: Vec&amp;lt;i32&amp;gt; = vec![1, 2, 3];
let num: &amp;amp;i32 = &amp;amp;v[2]; #L1
v.push(4); #L2
println!(&amp;quot;Third element is {}&amp;quot;, *num); #L3 error
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126183346.png&quot; alt=&quot;alt text&quot;&gt;
这个例子由于push之后v的容量超过了原来的容量，需要另外开辟一个空间并拷贝且增加容量，那么num这个别名就访问到了非法内存，在Rust中编译器会报错&lt;/p&gt;
&lt;p&gt;因为这违反了rust的原则：别名与可变不可以同时存在
在这个例子中也就是，在num存在的时候，v不能调用push方法，v没有写的权限W&lt;/p&gt;
&lt;p&gt;同时为了安全考虑，Rust的引用被设计之初就不是一种等同于别名的存在。&lt;/p&gt;
&lt;h2&gt;引用不等于别名&lt;/h2&gt;
&lt;p&gt;一个很好的解释说，Rust中的引用是临时创建的别名&lt;/p&gt;
&lt;p&gt;Box(有所有权的指针)：不能别名（上文中的第一条），但可被引用（上文中的第二条），若将一个Box变量赋值给另一个Box变量（把地址传给另一个变量，别名操作），只会发生移动，也就是所有权的转移&lt;/p&gt;
&lt;p&gt;不能让多个Box同时拥有一块数据&lt;/p&gt;
&lt;p&gt;引用(无所有权的指针)：旨在临时创建别名，把Box的地址传给一个引用变量，这个引用可以间接访问到Box指向的Heap内存&lt;/p&gt;
&lt;p&gt;由于println！会自动解引用&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn main(){
	let x = Box::new(1);
	let y = x;
	println!(&amp;quot;y: {}&amp;quot;,y);
	
	let r1 = &amp;amp;y;
	let r2 = &amp;amp;y;
	println!(&amp;quot;r1: {r1},r2: {r2}&amp;quot;);
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Rust中的权限&lt;/h1&gt;
&lt;h2&gt;Rust通过借用检测器确保引用的安全性&lt;/h2&gt;
&lt;p&gt;变量（数据所在地址）对其数据（地址的内容）有三种权限：
	读（R）：数据可以被复制到另一个位置
	写（W）：数据可以被修改
	拥有（O）：数据可以被移动或释放&lt;/p&gt;
&lt;p&gt;这些权限在运行时并不存在，仅在编译器内部存在&lt;/p&gt;
&lt;p&gt;默认情况下，变量对其数据具有读/拥有权限（RO）。
如果一个变量被注解为let mut,那么他还具有写权限（W）。&lt;/p&gt;
&lt;p&gt;关键：引用可以临时移除这些权限，所以引用有时也被叫做借用，把权限暂时借用走了&lt;/p&gt;
&lt;p&gt;补充：对于数组或者vec来说，引用其中的一个元素，这个元素地址的权限以及v都会受到影响，也就是&amp;amp;v[2]的权限和v的权限都变了&lt;/p&gt;
&lt;p&gt;对于不可变引用（共享引用）：引用只会让原变量失去W和O权限，也就是修改和释放权限&lt;/p&gt;
&lt;p&gt;比如这里的&amp;amp;v[2]他就是一个不可变引用&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126192331.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;p&gt;如何理解变量（数据所在地址）对其数据（地址的内容）这个限定
x_ref（一个地址）对地址的内容具有修改的权限而*x_ref（一个数0）则没有
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126195039.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h2&gt;权限是定义在位置上的&lt;/h2&gt;
&lt;p&gt;权限是定义在位置上的，不仅仅是单个变量
位置上任何可以防在赋值语句左侧的东西&lt;/p&gt;
&lt;p&gt;比如*((*a)[0].1),他就是一个位置，他具有权限这个定义&lt;/p&gt;
&lt;h2&gt;为什么失去特定权限&lt;/h2&gt;
&lt;p&gt;因为有些权限是互斥的&lt;/p&gt;
&lt;p&gt;怎么理解&lt;/p&gt;
&lt;p&gt;下面这个例子，当num有读权限R的时候，v就必然不能有O权限，因为num在访问v指向的Heap内存的时候，要保证v不能释放所指向的Heap内存，否则会出现未定义的行为&lt;/p&gt;
&lt;p&gt;同时为了防止数据竞争，当num有读权限R的时候，v就必然不能有W权限，防止num读的时候，v恰好修改Heap数据而造成的数据竞争，出现未定义的行为&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126195850.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;p&gt;比如在num使用的时候,就必然不能修改v指向的内存，下面这个例子就会报错，在num还存在的时候（还使用的时候）企图通过v来修改Heap内存&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126200513.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h2&gt;不可变引用和可变引用&lt;/h2&gt;
&lt;p&gt;以上都是说的不可变引用&lt;/p&gt;
&lt;p&gt;不可变引用可以存在多个，因为他们不违反：多个Box不能同时指向同一个Heap内存&lt;/p&gt;
&lt;p&gt;不可变引用（共享引用）：只读的&lt;/p&gt;
&lt;p&gt;如果只有不可变引用，我们要想修改一个Box类型指向的Heap内存，只能通过移动所有权的方式来用另一个变量修改&lt;/p&gt;
&lt;p&gt;因此我们还需要有可变引用&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;可变引用（独占引用）：在不移动数据的情况下，临时提供可变访问&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;同一作用域，特定数据只能有一个可变的引用&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;可变引用：可变引用提供对数据 “唯一的”(同一个作用域) 且 “非拥有的”访问
按道理来说可变引用也可以存在多个，因为他们不违反：多个Box不能同时指向同一个Heap内存，&lt;/p&gt;
&lt;p&gt;但是可变引用由于另一个原则，只能是唯一的&lt;/p&gt;
&lt;p&gt;我们说过权限是互斥的，当一个可变引用在修改Heap内存的时候，其他变量，不管是原变量还是另一个原变量的引用都不能再修改Heap内存了，因为如果是同时进行的话就会出现数据竞争&lt;/p&gt;
&lt;p&gt;也就是说，访问同一个Heap内存，只能有一个变量的权限有W,这也对应着，对于Box来说，别名和不可变性不能同时存在 &lt;/p&gt;
&lt;p&gt;同时可变引用也会让原变量失去R权限和O权限，O权限和不可变引用一样，而失去R权限还是为了防止数据竞争，在使用可变引用来修改Heap内存的时候，不能通过原变量来访问Heap内存，因为如果可变引用修改的时候原变量来访问，就会出现数据竞争，出现未定义对行为&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;下面是可变引用的例子
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126202054.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;p&gt;很多时候，大括号可以帮我们解决一些编译不通过的问题，通过手动限制变量的作用域：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let mut s = String::from(&amp;quot;hello&amp;quot;); 
 {  
   let r1 = &amp;amp;mut s; 
 } // r1 在这里离开了作用域，所以我们完全可以创建一个新的引用 
  let r2 = &amp;amp;mut s;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;不可变引用和可变引用对原变量权限的影响&lt;/h2&gt;
&lt;p&gt;由上面的例子，我们大致可以推断出这样一个原则&lt;/p&gt;
&lt;p&gt;不可变引用可以存在多个，他会让原变量失去WO权限，而引用本身获得R权限&lt;/p&gt;
&lt;p&gt;可变引用只能存在一个，他会让原变量失去RWO权限，而引用本身获得RW权限&lt;/p&gt;
&lt;p&gt;由以上还可以推断出&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;引用不能用来释放Heap,只有权限还给原变量的时候，才能通过原变量释放Heap&lt;/li&gt;
&lt;li&gt;原变量和引用一个有R另一个就没有W,一个有W另一个就不能有R&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;可变引用和不可变引用不能同时存在&lt;/h2&gt;
&lt;p&gt;下面的代码会导致一个错误：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let mut s = String::from(&amp;quot;hello&amp;quot;);

let r1 = &amp;amp;s; // 没问题
let r2 = &amp;amp;s; // 没问题
let r3 = &amp;amp;mut s; // 大问题

println!(&amp;quot;{}, {}, and {}&amp;quot;, r1, r2, r3);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;错误如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
        // 无法借用可变 `s` 因为它已经被借用了不可变
 --&amp;gt; src/main.rs:6:14
  |
4 |     let r1 = &amp;amp;s; // 没问题
  |              -- immutable borrow occurs here 不可变借用发生在这里
5 |     let r2 = &amp;amp;s; // 没问题
6 |     let r3 = &amp;amp;mut s; // 大问题
  |              ^^^^^^ mutable borrow occurs here 可变借用发生在这里
7 |
8 |     println!(&amp;quot;{}, {}, and {}&amp;quot;, r1, r2, r3);
  |                                -- immutable borrow later used here 不可变借用在这里使用
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其实这个也很好理解，正在借用不可变引用的用户，肯定不希望他借用的东西，被另外一个人莫名其妙改变了。多个不可变借用被允许是因为没有人会去试图修改数据，每个人都只读这一份数据而不做修改，因此不用担心数据被污染。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意，引用 &lt;code&gt;r1&lt;/code&gt;,&lt;code&gt;r2&lt;/code&gt;,&lt;code&gt;r3&lt;/code&gt; 的作用域从创建开始，一直持续到它最后一次使用的地方 &lt;code&gt;println!(....)&lt;/code&gt;，这个跟变量的作用域有所不同，变量的作用域从创建持续到某一个花括号 &lt;code&gt;}&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Rust 的编译器一直在优化，早期的时候，引用的作用域跟变量作用域是一致的，这对日常使用带来了很大的困扰，你必须非常小心的去安排可变、不可变变量的借用，免得无法通过编译，例如以下代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn main() {
   let mut s = String::from(&amp;quot;hello&amp;quot;);

    let r1 = &amp;amp;s;
    let r2 = &amp;amp;s;
    println!(&amp;quot;{} and {}&amp;quot;, r1, r2);
    // 新编译器中，r1,r2作用域在这里结束

    let r3 = &amp;amp;mut s;
    println!(&amp;quot;{}&amp;quot;, r3);
} // 老编译器中，r1、r2、r3作用域在这里结束
  // 新编译器中，r3作用域在这里结束
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在老版本的编译器中（Rust 1.31 前），将会报错，因为 &lt;code&gt;r1&lt;/code&gt; 和 &lt;code&gt;r2&lt;/code&gt; 的作用域在花括号 &lt;code&gt;}&lt;/code&gt; 处结束，那么 &lt;code&gt;r3&lt;/code&gt; 的借用就会触发 &lt;strong&gt;无法同时借用可变和不可变&lt;/strong&gt; 的规则。&lt;/p&gt;
&lt;p&gt;但是在新的编译器中，该代码将顺利通过，因为 &lt;strong&gt;引用作用域的结束位置从花括号变成最后一次使用的位置&lt;/strong&gt;，因此 &lt;code&gt;r1&lt;/code&gt; 借用和 &lt;code&gt;r2&lt;/code&gt; 借用在 &lt;code&gt;println!&lt;/code&gt; 后，就结束了，此时 &lt;code&gt;r3&lt;/code&gt; 可以顺利借用到可变引用。&lt;/p&gt;
&lt;h2&gt;可变引用临时降级为只读引用&lt;/h2&gt;
&lt;p&gt;还是同样的道理，如果先创建可变引用再创建不可变引用的话
可变引用会降级&lt;/p&gt;
&lt;p&gt;可以这么理解，要创建不可变引用，首先要保证创建的是不可变引用，所以只有R权限，而他的R和可变引用的W互斥，因为如果同时存在可能能发生数据竞争，所以可变引用被临时降级，当不可变引用灭亡时，可变引用重新获得W权限&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126211316.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;p&gt;曾经的疑惑
为什么下面一串代码会报错&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn main() {
	let mut v: Vec&amp;lt;i32&amp;gt; = vec![1,2,3];
	let num : &amp;amp;mut i32 = &amp;amp;mut v[2];
	let num2 : &amp;amp; i32 = &amp;amp;v[2];
	println!(&amp;quot;{} {}&amp;quot;, num, num2);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其实就是Rust借用检测器关注的是借用的来源，如果这么写就相当于有一个可变引用和一个不可变引用在同一作用域，并且他们的根为v[2]&lt;/p&gt;
&lt;p&gt;而原来的写法相当于一个可变引用根为v[2],一个不可变引用根为num，这叫做派生借用，也叫子借用&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126214801.png&quot; alt=&quot;alt text&quot;&gt;
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126214952.png&quot; alt=&quot;alt text&quot;&gt;
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126215005.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h1&gt;悬垂引用问题&lt;/h1&gt;
&lt;p&gt;悬垂引用也叫做悬垂指针，意思为指针指向某个值后，这个值被释放掉了，而指针仍然存在，其指向的内存可能不存在任何值或已被其它变量重新使用。在 Rust 中编译器可以确保引用永远也不会变成悬垂状态：当你获取数据的引用后，编译器可以确保数据不会在引用结束前被释放，要想释放数据，必须先停止其引用的使用。&lt;/p&gt;
&lt;p&gt;让我们尝试创建一个悬垂引用，Rust 会抛出一个编译时错误：
`&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -&amp;gt; &amp;amp;String {
    let s = String::from(&amp;quot;hello&amp;quot;);

    &amp;amp;s
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里是错误：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;error[E0106]: missing lifetime specifier
 --&amp;gt; src/main.rs:5:16
  |
5 | fn dangle() -&amp;gt; &amp;amp;String {
  |                ^ expected named lifetime parameter
  |
  = help: this function&amp;#39;s return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `&amp;#39;static` lifetime
  |
5 | fn dangle() -&amp;gt; &amp;amp;&amp;#39;static String {
  |                ~~~~~~~~
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;错误信息引用了一个我们还未介绍的功能：&lt;a href=&quot;https://course.rs/basic/lifetime.html&quot;&gt;生命周期(lifetimes)&lt;/a&gt;。不过，即使你不理解生命周期，也可以通过错误信息知道这段代码错误的关键信息：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fn dangle() -&amp;gt; &amp;amp;String { // dangle 返回一个字符串的引用

    let s = String::from(&amp;quot;hello&amp;quot;); // s 是一个新字符串

    &amp;amp;s // 返回字符串 s 的引用
} // 这里 s 离开作用域并被丢弃。其内存被释放。
  // 危险！
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;仔细看看 &lt;code&gt;dangle&lt;/code&gt; 代码的每一步到底发生了什么：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn dangle() -&amp;gt; &amp;amp;String { // dangle 返回一个字符串的引用

    let s = String::from(&amp;quot;hello&amp;quot;); // s 是一个新字符串

    &amp;amp;s // 返回字符串 s 的引用
} // 这里 s 离开作用域并被丢弃。其内存被释放。
  // 危险！
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为 &lt;code&gt;s&lt;/code&gt; 是在 &lt;code&gt;dangle&lt;/code&gt; 函数内创建的，当 &lt;code&gt;dangle&lt;/code&gt; 的代码执行完毕后，&lt;code&gt;s&lt;/code&gt; 将被释放，但是此时我们又尝试去返回它的引用。这意味着这个引用会指向一个无效的 &lt;code&gt;String&lt;/code&gt;，这可不对！&lt;/p&gt;
&lt;p&gt;其中一个很好的解决方法是直接返回 &lt;code&gt;String&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn no_dangle() -&amp;gt; String {
    let s = String::from(&amp;quot;hello&amp;quot;);

    s
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样就没有任何错误了，最终 &lt;code&gt;String&lt;/code&gt; 的 &lt;strong&gt;所有权被转移给外面的调用者&lt;/strong&gt;。&lt;/p&gt;
&lt;h1&gt;借用规则总结&lt;/h1&gt;
&lt;p&gt;总的来说，借用规则如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;同一时刻，你只能拥有要么一个可变引用，要么任意多个不可变引用，可变引用和不可变引用不能同时存在&lt;/li&gt;
&lt;li&gt;引用必须总是有效的&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;F权限流动权限&lt;/h1&gt;
&lt;p&gt;最后再补充一下&lt;/p&gt;
&lt;p&gt;流动权限F：在表达式使用输入引用或返回输出引用时需要&lt;/p&gt;
&lt;p&gt;F权限在函数体内不会发生变化&lt;/p&gt;
&lt;p&gt;如果一个引用被允许在特定表达式中使用（即流动），那么它就具有F权限&lt;/p&gt;
&lt;p&gt;我的理解是，流动权限是决定你能不能在不同表达式之间“流动”的一种权限，如果可以那么就具有流动权限，如果不可以就不具有流动权限&lt;/p&gt;
&lt;p&gt;第一行的引用是输入引用（也就是函数某一参数的引用）需要流动权限，而他也具有流动权限
第二行返回输出引用，也需要流动权限，而他也具有流动权限
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126222118.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;p&gt;这个例子就会报错，因为Rust检查输出引用的返回时只看函数的签名，而这个例子中Rust只看这个函数签名，不知道&amp;amp;String返回的是引用自谁的引用，可能是strings的，也可能是default的，由于这种不确定性，可能会导致未定义的行为，比如下面的例子可能造成UAF,drop为释放Box指向的Heap内存&lt;/p&gt;
&lt;p&gt;可见&amp;amp;string[0]和default的输出引用都不具有流动权限&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126222357.png&quot; alt=&quot;alt text&quot;&gt;
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126222427.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;p&gt;这个例子就是我们上面说的悬挂引用的例子
s_ref输出引用显然不具有流动权限
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126223009.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
 &lt;blockquote&gt;This rendering was automatically generated by Frosti Feed and may have formatting issues. For the best experience, please visit: &lt;a href=&quot;https://c4e-i-um-github-io.vercel.app/blog/rust%E5%BC%95%E7%94%A8%E5%92%8C%E5%80%9F%E7%94%A8/&quot;&gt;https://c4e-i-um-github-io.vercel.app/blog/rust%E5%BC%95%E7%94%A8%E5%92%8C%E5%80%9F%E7%94%A8/&lt;/a&gt;&lt;/blockquote&gt;</content:encoded><dc:creator>Caelum</dc:creator><pubDate>Tue, 27 Jan 2026 00:00:00 GMT</pubDate></item><item><title>Rust所有权</title><link>https://c4e-i-um-github-io.vercel.app/blog/rust%E6%89%80%E6%9C%89%E6%9D%83%E6%9C%BA%E5%88%B6/</link><guid isPermaLink="true">https://c4e-i-um-github-io.vercel.app/blog/rust%E6%89%80%E6%9C%89%E6%9D%83%E6%9C%BA%E5%88%B6/</guid><description>Rust所有权</description><content:encoded>&lt;p&gt;rust为了防止出现未定义的行为引入了所有权机制，极大的保障了内存的安全&lt;/p&gt;
&lt;h1&gt;概述&lt;/h1&gt;
&lt;h2&gt;未定义行为&lt;/h2&gt;
&lt;p&gt;未定义行为：当执行一段代码时，结果不可预测且未被语言指定的情况&lt;/p&gt;
&lt;p&gt;比如数组越界访问，访问释放的堆内存，释放两次堆内存等&lt;/p&gt;
&lt;h2&gt;Rust的目标&lt;/h2&gt;
&lt;p&gt;基础目标：确保程序永远不会有未定义的行为
次要目标：在编译的时候而不是运行的时候防止未定义行为&lt;/p&gt;
&lt;h1&gt;所有权&lt;/h1&gt;
&lt;h2&gt;局部变量在stack中&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn main(){
	let n = 5;    #L1
	let y = plus_one(n);    #L3
	println!(&amp;quot;The value of y is: {y}&amp;quot;);
}

fn plus_one(x: i32) -&amp;gt; i32{
	x + 1        #L2
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126105539.png&quot; alt=&quot;Pasted image 20260126105539&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Box存活在Heap中&lt;/h2&gt;
&lt;p&gt;如果不使用Box,也就是在Stack上分配，会浪费很多空间&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn main(){
	let a = [0;1_000_000];  #L1
	let b = a;     #L2
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126105752.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;p&gt;如果使用Box在Heap上分配，Stack上存放的只是指针，指向同一个Heap地址，其他语言一般通过这两个指针都可以访问这个Heap内存，但在Rust中，由于所有权的概念，同时最多只能有一个指针能访问指向的Heap内存。&lt;/p&gt;
&lt;p&gt;一开始我们说a具有所有权，到后来let b = a; 我们说，a把所有权转移给了b,此时只能通过b来访问Heap,如果企图通过a来访问，rust编辑器会报错。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let a = Box::new([0;1_000_000]);  #L1
let b = a;   #L2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126110422.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;p&gt;需要注意的是这种情况&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let a = Box::new(15);
let b = a;
let c = Box::new(15);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种情况是另开了一个Box，不是所有权的转移&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126110610.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h1&gt;Rust内存管理策略&lt;/h1&gt;
&lt;h2&gt;Rust不允许手动内存管理&lt;/h2&gt;
&lt;p&gt;Stack Frame由Rust自动管理：当调用一个函数时，Rust为调用的函数分配一个Stack Frame。当调用结束时，Rust释放该Stack Frame&lt;/p&gt;
&lt;p&gt;假设有一段代码,这段代码会由于手动释放了b所在的Stack内存，然后rust在函数调用结束后又自动释放了b所在的Stack内存，这就出现了Double Free,为了防止这样的未定义行为，Rust不允许手动内存管理&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let b = Box::new([0;100]);
free(b);
assert!(b[0] == 0);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126113208.png&quot; alt=&quot;Pasted image 20260126113208&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Box的真正拥有者来管理对应Box内存的释放&lt;/h2&gt;
&lt;p&gt;Rust会自动释放Box的Heap内存&lt;/p&gt;
&lt;p&gt;Box内存释放原则：如果一个变量拥有（所有）一个Box,当Rust释放该变量的Stack Frame时，Rust会释放该Box的Heap内存。&lt;/p&gt;
&lt;p&gt;而使用Box的集合有：Vec,String, &amp;amp;HashMap等&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn main(){
	let first = String::from(&amp;quot;Ferris&amp;quot;);  #L1
	let full = add_suffix(first); #L4
}

fn add_suffix(mut name: String) -&amp;gt; String{
	#L2
	name.push_str(&amp;quot;Jr.&amp;quot;);  #L3
	name
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126114156.png&quot; alt=&quot;alt text&quot;&gt;
所有权的转移：first  -&amp;gt;   name   -&amp;gt;   full&lt;/p&gt;
&lt;h2&gt;移动&lt;/h2&gt;
&lt;p&gt;移动：如果变量x将Heap内存的所有权给了另一个变量y,也就是发生了所有权的转移，这就叫移动，所有权转移后x将不再能访问原来的Heap内存。&lt;/p&gt;
&lt;h2&gt;克隆&lt;/h2&gt;
&lt;p&gt;而与移动相对应的就是克隆&lt;/p&gt;
&lt;p&gt;避免数据移动的一种方法是使用.clone()方法进行克隆&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn main(){
	let first = String::from(&amp;quot;Ferris&amp;quot;);
	let fist_clone = first.clone(); #L1
	let full = add_suffix(first_clone); #L2
	println!(&amp;quot;{full},originally {first}&amp;quot;); 
}

fn add_suffix(mut name: String) -&amp;gt; String{
	name.push_str(&amp;quot;Jr.&amp;quot;);
	name
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/rust/Pasted%20image%2020260126115343.png&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
 &lt;blockquote&gt;This rendering was automatically generated by Frosti Feed and may have formatting issues. For the best experience, please visit: &lt;a href=&quot;https://c4e-i-um-github-io.vercel.app/blog/rust%E6%89%80%E6%9C%89%E6%9D%83%E6%9C%BA%E5%88%B6/&quot;&gt;https://c4e-i-um-github-io.vercel.app/blog/rust%E6%89%80%E6%9C%89%E6%9D%83%E6%9C%BA%E5%88%B6/&lt;/a&gt;&lt;/blockquote&gt;</content:encoded><dc:creator>Caelum</dc:creator><pubDate>Mon, 26 Jan 2026 00:00:00 GMT</pubDate></item><item><title>glibc2.39 free总结</title><link>https://c4e-i-um-github-io.vercel.app/blog/glibc239-free%E6%80%BB%E7%BB%93/</link><guid isPermaLink="true">https://c4e-i-um-github-io.vercel.app/blog/glibc239-free%E6%80%BB%E7%BB%93/</guid><description>glibc2.39 free总结</description><content:encoded>&lt;ol&gt;
&lt;li&gt;&lt;p&gt;执行free进入_int_free函数&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;检查,如果free的地址p大于(uintptr_t) -size或者p不是0x10对齐的，报错&amp;quot;free(): invalid pointer&amp;quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;如果根据p获取到的size，小于0x20或者size不是8字节对齐，则报错&amp;quot;free(): invalid size&amp;quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;检查下一个size的prev_inuse位&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;进入free 进入tcache的流程&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;根据size获取tc_idx&lt;/li&gt;
&lt;li&gt;如果tcache已经初始化，并且tc_idx&amp;lt; mp_.tcache_bins&lt;ol&gt;
&lt;li&gt;获取p指向堆块的头,p原来指的是0x10偏移的位置,这个地址为e&lt;/li&gt;
&lt;li&gt;如果e的key等于tcache_key（一个随机数），就进入检查&lt;ol&gt;
&lt;li&gt;循环遍历该tcache，如果循环计数器cnt大于mp_.tcache_count每个tcache存放堆块的最大个数，报错&amp;quot;free(): too many chunks detected in tcache&amp;quot;&lt;/li&gt;
&lt;li&gt;如果tcache里有堆块没对齐，报错&amp;quot;free(): unaligned chunk detected in tcache 2&amp;quot;&lt;/li&gt;
&lt;li&gt;如果e等于tmp，就是存在两个相同堆块，报错&amp;quot;free(): double free detected in tcache 2&amp;quot;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;如果对应tc_idx的tcache没满，放入tcache，返回&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;进入free进入fastbin的流程&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果size，小于等于MAX_FAST，并且p的下一个堆块不是top&lt;ol&gt;
&lt;li&gt;检查，如果下一个堆块的size小于0x10或者大于av-&amp;gt;system_mem，则报错&amp;quot;free(): invalid next size (fast)&amp;quot;&lt;/li&gt;
&lt;li&gt;让av-&amp;gt;have_fastchunks变为true&lt;/li&gt;
&lt;li&gt;根据size获取对应的fastbin的idx&lt;/li&gt;
&lt;li&gt;获取对应fastbin的地址，fb&lt;/li&gt;
&lt;li&gt;获取fastbin最后放入的堆块，为old&lt;/li&gt;
&lt;li&gt;如果释放的和old是同一个堆块，报错&amp;quot;double free or corruption (fasttop)&amp;quot;&lt;/li&gt;
&lt;li&gt;加密释放堆块的fd&lt;/li&gt;
&lt;li&gt;放入fastbin&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;否则如果不是mmap的堆块，进入_int_free_merge_chunk函数&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;根据size，获取p的nextchunk&lt;/li&gt;
&lt;li&gt;如果p是topchunk，报错&amp;quot;double free or corruption (top)&amp;quot;&lt;/li&gt;
&lt;li&gt;如果arena是sbrk分配的，并且nextchunk的地址&amp;gt;av-&amp;gt;top加上top的size，报错&amp;quot;double free or corruption (out)&amp;quot;&lt;/li&gt;
&lt;li&gt;如果下一个堆块的pre_inuse为0，报错&amp;quot;double free or corruption (!prev)&amp;quot;&lt;/li&gt;
&lt;li&gt;如果下一个堆块的size&amp;lt;0x10或者下一个堆块的size&amp;gt;=av-&amp;gt;system_mem,报错&amp;quot;free(): invalid next size (normal)&amp;quot;&lt;/li&gt;
&lt;li&gt;进行向后合并&lt;ol&gt;
&lt;li&gt;如果p的prev_inuse为0&lt;/li&gt;
&lt;li&gt;获取p的prev_size,为prevsize&lt;/li&gt;
&lt;li&gt;根据prevsize更新p为上一个堆块&lt;/li&gt;
&lt;li&gt;如果现在的p的size，与刚刚保存的prev_size不同，报错&amp;quot;corrupted size vs. prev_size while consolidating&amp;quot;&lt;/li&gt;
&lt;li&gt;unlink现在的p&lt;ol&gt;
&lt;li&gt;如果堆块的size位不等于（根据size找到的）下一个堆块的pre_size 报错 &amp;quot;corrupted size vs. prev_size&amp;quot;&lt;/li&gt;
&lt;li&gt;更新fd为p的fd，bk为p的bk&lt;/li&gt;
&lt;li&gt;如果fd-&amp;gt;bk!=p或者bk-&amp;gt;fd!=p报错&amp;quot;corrupted double-linked list&amp;quot;&lt;/li&gt;
&lt;li&gt;执行fd和bk的unlink过程&lt;ol&gt;
&lt;li&gt;fd-&amp;gt;bk = bk;&lt;/li&gt;
&lt;li&gt;bk-&amp;gt;fd = fd;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;如果这个堆块不属于small bin的大小范围,并且这个堆块的fd_nextsize不等于NULL，则会进入unlink largebin的过程&lt;ol&gt;
&lt;li&gt;如果p-&amp;gt;fd_nextsize-&amp;gt;bk_nextsize != p或者p-&amp;gt;bk_nextsize-&amp;gt;fd_nextsize != p报错&amp;quot;corrupted double-linked list (not small)&amp;quot;&lt;/li&gt;
&lt;li&gt;如果fd-&amp;gt;fd_nextsize == NULL&lt;ol&gt;
&lt;li&gt;如果p-&amp;gt;fd_nextsize == p&lt;ol&gt;
&lt;li&gt;fd-&amp;gt;fd_nextsize = fd-&amp;gt;bk_nextsize = fd;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;否则&lt;ol&gt;
&lt;li&gt;fd-&amp;gt;fd_nextsize = p-&amp;gt;fd_nextsize;&lt;/li&gt;
&lt;li&gt;fd-&amp;gt;bk_nextsize = p-&amp;gt;bk_nextsize;&lt;/li&gt;
&lt;li&gt;p-&amp;gt;fd_nextsize-&amp;gt;bk_nextsize = fd;&lt;/li&gt;
&lt;li&gt;p-&amp;gt;bk_nextsize-&amp;gt;fd_nextsize = fd;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;否则&lt;ol&gt;
&lt;li&gt;p-&amp;gt;fd_nextsize-&amp;gt;bk_nextsize = p-&amp;gt;bk_nextsize;&lt;/li&gt;
&lt;li&gt;p-&amp;gt;bk_nextsize-&amp;gt;fd_nextsize = p-&amp;gt;fd_nextsize;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;如果是mmap的堆块，munmap它&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
 &lt;blockquote&gt;This rendering was automatically generated by Frosti Feed and may have formatting issues. For the best experience, please visit: &lt;a href=&quot;https://c4e-i-um-github-io.vercel.app/blog/glibc239-free%E6%80%BB%E7%BB%93/&quot;&gt;https://c4e-i-um-github-io.vercel.app/blog/glibc239-free%E6%80%BB%E7%BB%93/&lt;/a&gt;&lt;/blockquote&gt;</content:encoded><dc:creator>Caelum</dc:creator><pubDate>Sat, 01 Nov 2025 00:00:00 GMT</pubDate></item><item><title>glibc2.39 malloc总结</title><link>https://c4e-i-um-github-io.vercel.app/blog/glibc239-malloc%E6%80%BB%E7%BB%93/</link><guid isPermaLink="true">https://c4e-i-um-github-io.vercel.app/blog/glibc239-malloc%E6%80%BB%E7%BB%93/</guid><description>glibc2.39 malloc总结</description><content:encoded>&lt;ol&gt;
&lt;li&gt;malloc首先进入__libc_malloc&lt;/li&gt;
&lt;li&gt;在__libc_malloc中首先申请的是tcache bin&lt;ol&gt;
&lt;li&gt;如果没初始化tcache就初始化&lt;/li&gt;
&lt;li&gt;检查：&lt;ol&gt;
&lt;li&gt;检查请求的大小对应的idx，是不是小于tcache_bins的个数&lt;/li&gt;
&lt;li&gt;检查tcache是否不为空&lt;/li&gt;
&lt;li&gt;检查tcache-&amp;gt;counts[tc_idx] 是否大于0&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;如果检查通过那么就从tcache中取堆块，然后返回给用户，不进入_int_malloc&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;进入_int_malloc&lt;ol&gt;
&lt;li&gt;获取0x10对齐后的大小，后简记为nb&lt;/li&gt;
&lt;li&gt;如果av等于NULL，那么sysmalloc一个&lt;/li&gt;
&lt;li&gt;如果nb小于等于宏定义中的MAX_FAST,进入fastbin的申请流程&lt;ol&gt;
&lt;li&gt;根据nb，获取对应fastbin的idx&lt;/li&gt;
&lt;li&gt;获取对应idx的fastbin的地址，fb&lt;/li&gt;
&lt;li&gt;victim等于*fb,也就是fastbin的最后放入的一个堆块&lt;/li&gt;
&lt;li&gt;如果victim不为NULL&lt;ol&gt;
&lt;li&gt;检查是否0x10对齐，不对齐就报错&amp;quot;malloc(): unaligned fastbin chunk detected 2&lt;/li&gt;
&lt;li&gt;还原已经xor的指针&lt;/li&gt;
&lt;li&gt;如果victim不为NULL（一般都会执行）&lt;ol&gt;
&lt;li&gt;根据victim的size大小获取idx，victim_idx&lt;/li&gt;
&lt;li&gt;如果victim_idx !=idx，就会报错&amp;quot;malloc(): memory corruption (fast)&amp;quot;&lt;/li&gt;
&lt;li&gt;如果tcache已经初始化，并且获取到的tc_idx小于tcache_bins的个数&lt;ol&gt;
&lt;li&gt;只要对应tc_idx的tcache没满，fastbin中还有堆块，那么就会让tc_victim等于fastbin最后释放的堆块&lt;ol&gt;
&lt;li&gt;如果tc_victim不对齐，报错&amp;quot;malloc(): unaligned fastbin chunk detected 3&amp;quot;&lt;/li&gt;
&lt;li&gt;如果是单线程&lt;ol&gt;
&lt;li&gt;还原xor的fastbin指针&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;放入对应tc_idx的tcache（原来的victim也被放入，根据头插法，会被放到所有fastbin堆块最后）&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;直到不满足条件结束循环&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;返回victim&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;如果nb大于 MAX_FAST，并且属于smallbin的范围&lt;ol&gt;
&lt;li&gt;获取nb对应smallbin的idx&lt;/li&gt;
&lt;li&gt;获取对应idx的samllbin的地址，bin&lt;/li&gt;
&lt;li&gt;如果smallbin不为空，同时&lt;ol&gt;
&lt;li&gt;让victim等于smallbin尾部的堆块，也就是最先放入的堆块&lt;/li&gt;
&lt;li&gt;获取victim的bk指向的堆块，bck&lt;/li&gt;
&lt;li&gt;如果bck的fd指针不指向victim，报错&amp;quot;malloc(): smallbin double linked list corrupted&amp;quot;&lt;/li&gt;
&lt;li&gt;为下一个堆块设置pre_inuse标志位&lt;/li&gt;
&lt;li&gt;让bin的bk连上bck，让后面的fd连上bin，也就是victim从smallbin中拿出&lt;/li&gt;
&lt;li&gt;根据nb获取对应的tcache的tc_idx&lt;/li&gt;
&lt;li&gt;如果tcache已经初始化，并且tc_idx小于tcache_bins（其实就是在tcache的范围内，tcache_bins可以理解为管控tcache的大小范围的）&lt;ol&gt;
&lt;li&gt;只要tcachebin没满并且smallbin不为空，不断取出循环放入tcache（同样根据头插法victim也会被放入tcache，并且是最后）&lt;/li&gt;
&lt;li&gt;直到不满足条件，循环结束&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;返回用户堆块&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;如果上述条件不满足&lt;ol&gt;
&lt;li&gt;根据nb，获取largebin对应的idx&lt;/li&gt;
&lt;li&gt;如果fastbin不为空&lt;ol&gt;
&lt;li&gt;触发malloc_consolidate，合并fastbin放入unsorted bin&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;初始化一些tcache的东西，为下面进入unsorted bin大循环做准备&lt;ol&gt;
&lt;li&gt;根据nb获取对应tc_idx&lt;/li&gt;
&lt;li&gt;如果tcache已经初始化，并且tc_idx小于tcache_bins&lt;/li&gt;
&lt;li&gt;让tcache_nb=nb;&lt;/li&gt;
&lt;li&gt;让return_cached = 0;标志位，标记是否直接从 tcache 返回内存&lt;/li&gt;
&lt;li&gt;让tcache_unsorted_count = 0;  用于统计本次 _int_malloc 从 unsorted bin 移入 tcache 的 chunk 数量&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;进入unsorted bin大循环，最多循环1000次&lt;ol&gt;
&lt;li&gt;初始化计数器iters = 0&lt;/li&gt;
&lt;li&gt;只要unsorted bin不为空&lt;ol&gt;
&lt;li&gt;victim等于unsorted bin最后放入的堆块&lt;/li&gt;
&lt;li&gt;bck = victim的bk指向的堆块&lt;/li&gt;
&lt;li&gt;获取victim的size，size&lt;/li&gt;
&lt;li&gt;根据size，获取下一个堆块的地址，next&lt;/li&gt;
&lt;li&gt;然后是一大堆检查&lt;ol&gt;
&lt;li&gt;如果该堆块victim的size&amp;lt;0x10或者size&amp;gt;arena的大小就会报错&amp;quot;malloc(): invalid size (unsorted)&amp;quot;&lt;/li&gt;
&lt;li&gt;如果下一个堆块next的size&amp;lt;0x10或者size&amp;gt;arena的大小就会报错&amp;quot;malloc(): invalid next size (unsorted)&amp;quot;&lt;/li&gt;
&lt;li&gt;如果next的prev_size不等于victim的size就报错&amp;quot;malloc(): mismatching next-&amp;gt;prev_size (unsorted)&amp;quot;&lt;/li&gt;
&lt;li&gt;如果bck的fd不等于victim，或者victim的fd不等于unsorted bin就报错&amp;quot;malloc(): unsorted double linked list corrupted&amp;quot;&lt;/li&gt;
&lt;li&gt;如果next的prev_inuse为1，就报错&amp;quot;malloc(): invalid next-&amp;gt;prev_inuse (unsorted)&amp;quot;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;如果这个堆块属于unsorted bin中smallbin的范围，并且unsorted bin只有这一个堆块，并且这个堆块还是上一次切割剩下的堆块，也就是last remainder，并且size&amp;gt;nb+0x20&lt;ol&gt;
&lt;li&gt;再次切割这个堆块&lt;ol&gt;
&lt;li&gt;remainder_size = size - nb;然后remainder = chunk_at_offset (victim, nb);此时victim等于切割下来的堆块&lt;/li&gt;
&lt;li&gt;保存remainder到last_remainder中，last_remainder=remainder&lt;/li&gt;
&lt;li&gt;如果切割后还属于smallbin的范围，把fd_nextsize和bk_nextsize置为NULL&lt;/li&gt;
&lt;li&gt;给victim设置头部信息，给remainder设置头部信息，给remainder的下一个堆块写入prev_size&lt;/li&gt;
&lt;li&gt;进行检查&lt;ol&gt;
&lt;li&gt;检查victim是否是mmap的&lt;/li&gt;
&lt;li&gt;检查arenna的地址&lt;/li&gt;
&lt;li&gt;检查victim是否对齐&lt;/li&gt;
&lt;li&gt;检查victim是否size&amp;gt;=0x20&lt;/li&gt;
&lt;li&gt;检查victim的大小是否≥nb&lt;/li&gt;
&lt;li&gt;检查victim的大小是否 &amp;lt;nb+0x20&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;返回用户堆块&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;如果不切割，把victim拿出，unsorted_chunks (av)-&amp;gt;bk = bck; bck-&amp;gt;fd = unsorted_chunks (av);&lt;/li&gt;
&lt;li&gt;如果victim的size等于nb&lt;ol&gt;
&lt;li&gt;给victim的下一个堆块设置prev_inuse位&lt;/li&gt;
&lt;li&gt;如果nb&amp;gt;0，并且对应idx的tcache还没满&lt;ol&gt;
&lt;li&gt;把victim放入对应idx的tcache，然后继续循环取和放入，直到tcache满或者unsorted bin为空了&lt;/li&gt;
&lt;li&gt;让return_cached = 1，标志一会要从tcache bin返回堆块&lt;/li&gt;
&lt;li&gt;continue直接跳出while循环，跳到 如果return_cached = 1，从tcache_get获取&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;否则&lt;ol&gt;
&lt;li&gt;检查是否是mmap的如果是不进行接下来的检查，检查arenna的地址，检查是否对齐，检查是否size&amp;gt;=0x20,    检查victim的大小是否&amp;gt;=用户申请的大小，检查victim的大小是否&amp;lt;用户申请的大小+0x20&lt;/li&gt;
&lt;li&gt;返回victim给用户&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;否则说明unsorted bin中没有合适的，把unsorted bin中的堆块放入对应大小的bin中，接下来就是这一过程，这个过程是沿着unsorted bin中的fd链的，如果第一个是0x420，第二个是0x430，第三个是0x410，那么malloc 0x430以后，0x420的堆块会进入large bin，0x430的堆块会被取出返回，0x410的堆块会呆在unsorted bin，参考上面victim的size等于nb的情况&lt;/li&gt;
&lt;li&gt;如果victim的size属于smallbin的范围&lt;ol&gt;
&lt;li&gt;根据size，获取对应smallbin的下标，victim_index&lt;/li&gt;
&lt;li&gt;bck等于对应victim的idx的smallbin地址&lt;/li&gt;
&lt;li&gt;fwd为从smallbin最后放入的堆块&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;如果victim的size属于largebin的范围(下面是更新fd_nextsize和bk_nextsize)
 1.  根据size，获取对应largebin的下标，victim_index
 2. bck等于对应victim的idx的largebin地址
 3.  fwd为从largebin最后放入的堆块（即largebin中最大 chunk ）&lt;ol&gt;
&lt;li&gt;如果idx对应的largebin不为空&lt;ol&gt;
&lt;li&gt;将victim的size的prev_inuse位设置为1&lt;/li&gt;
&lt;li&gt;如果victim的size&amp;lt;对应largebin放入的最先一个堆块（当前large bin中最小的chunk时），那么认定victim更小，因此需要将它插入到 Large Bin 的末尾（即最小 chunk 之后），把victim找到合适位置放入largebin并更新十字链表的指针&lt;ol&gt;
&lt;li&gt;更新fwd等于largebin的地址&lt;/li&gt;
&lt;li&gt;bck为该largebin最先的第二个放入的堆块当前 Large Bin 中第二大的 chunk&lt;/li&gt;
&lt;li&gt;让victim的fd_nextsize指向该largebin最后放入的堆块（当前 Large Bin 中最大的 chunk），这里是循环链表的缘故，victim最小了再小就指向最大的了&lt;/li&gt;
&lt;li&gt;让victim的bk_nextsize指向该largebin最后放入的堆块（当前 Large Bin 中最大的 chunk）的bk_nextsize&lt;/li&gt;
&lt;li&gt;该largebin最后放入的堆块（当前 Large Bin 中最大的 chunk）的bk_nextsize指向victim&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;否则&lt;ol&gt;
&lt;li&gt;只要size小于即fwd（largebin中最大 chunk ），同时大于等于该large bin中最小的chunk&lt;ol&gt;
&lt;li&gt;一直去寻找更小的堆块，&lt;/li&gt;
&lt;li&gt;更新fwd=fwd-&amp;gt;fd_nextsize&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;直到找到刚好小于等于victim size的chunk,此时fwd为这个chunk&lt;/li&gt;
&lt;li&gt;如果victim的size正好等于这个chunk的size&lt;ol&gt;
&lt;li&gt;更新fwd 等于 fwd的fd，准备把victim放到fwd的fd位置&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;否则&lt;ol&gt;
&lt;li&gt;则说明victim要放到fwd的bk_nextsize位置&lt;/li&gt;
&lt;li&gt;victim的fd_nextsize指针指向这个chunk&lt;/li&gt;
&lt;li&gt;victim的bk_nextsize指针指向这个chunk的bk_nextsize&lt;/li&gt;
&lt;li&gt;检查，如果这个chunk的bk_nextsize的fd_nextsize不是这个堆块报错&amp;quot;malloc(): largebin double linked list corrupted (nextsize)&amp;quot;&lt;/li&gt;
&lt;li&gt;这个堆块（fwd）的bk_nextsize指向victim&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;更新bck为fwd（此时的堆块）的bk指向的堆块&lt;/li&gt;
&lt;li&gt;检查，如果bck的fd不等于fwd报错&amp;quot;malloc(): largebin double linked list corrupted (bk)&amp;quot;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;否则&lt;ol&gt;
&lt;li&gt;让victim的fd_nextsize和bk_nextsize都指向victim&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;victim-&amp;gt;bk = bck;   victim-&amp;gt;fd = fwd; fwd-&amp;gt;bk = victim; bck-&amp;gt;fd = victim，victim连接fwd和bck，它们再连接victim&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;让tcache_unsorted_count++  tcache_unsorted_count代表tcacge中unsorted bin数量&lt;/li&gt;
&lt;li&gt;如果return_cached = 1并且mp_.tcache_unsorted_limit （是 tcache中unsorted bin的 限制值通常为0）&amp;gt; 0并且tcache_unsorted_count大于mp_.tcache_unsorted_limit&lt;ol&gt;
&lt;li&gt;直接从tcache中拿堆块，并返回用户堆块&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;如果循环大于1000次，退出循环&lt;/li&gt;
&lt;li&gt;如果return_cached = 1，从tcache_get获取，并返回&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;接下来准备从largebin中拿&lt;/li&gt;
&lt;li&gt;如果nb属于large bin的范围&lt;ol&gt;
&lt;li&gt;获取对应large bin的地址，bin&lt;/li&gt;
&lt;li&gt;如果largebin不为空，让victim变为largebin最后放入的堆块并且如果victim的size大于等于nb（意味着肯定是可以被分配的）&lt;ol&gt;
&lt;li&gt;让victim变为large bin中最小的&lt;/li&gt;
&lt;li&gt;只要victim的size小于nb&lt;ol&gt;
&lt;li&gt;就去寻找更大的&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;直到victim的size大于等于nb&lt;/li&gt;
&lt;li&gt;如果victim不是largebin最小的堆块并且victim的size等于victim-&amp;gt;fd&lt;ol&gt;
&lt;li&gt;选择切割后放入(victim-&amp;gt;fd)的而不是先放入的(victim)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;切割，remainder_size = size - nb&lt;/li&gt;
&lt;li&gt;让victim脱链unlink&lt;/li&gt;
&lt;li&gt;如果切割后剩下的小于 0x20&lt;ol&gt;
&lt;li&gt;给下一个堆块设置prev_inuse位&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;否则&lt;ol&gt;
&lt;li&gt;更新remainder为切割后的堆块，victim为切割下来的堆块&lt;/li&gt;
&lt;li&gt;获取unsorted_chunks的地址，bck&lt;/li&gt;
&lt;li&gt;更新fwd为unsorted bin中最后放入的堆块&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;检查，如果fwd的bk不指向bck，报错&amp;quot;malloc(): corrupted unsorted chunks&amp;quot;&lt;ol&gt;
&lt;li&gt;让切割剩下的remainder放入unsorted bin的头部&lt;/li&gt;
&lt;li&gt;如果remainder属于large bin的范围&lt;ol&gt;
&lt;li&gt;把fd_nextsize和bk_nextsize设置为NULL&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;为victim设置头部信息，为remainder设置头部信息，为remainder的下一个 堆块设置prev_size&lt;/li&gt;
&lt;li&gt;常规检查&lt;/li&gt;
&lt;li&gt;返回用户堆块&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;位图再次搜索，如果有可用堆块，返回用户堆块&lt;/li&gt;
&lt;li&gt;如果上述都没能返回用户堆块，使用top chunk&lt;ol&gt;
&lt;li&gt;victim = av-&amp;gt;top ,获取top chunk的地址&lt;/li&gt;
&lt;li&gt;根据victim获取size&lt;/li&gt;
&lt;li&gt;检查，如果size&amp;gt;av-&amp;gt;system_mem，报错&amp;quot;malloc(): corrupted top size&amp;quot;&lt;/li&gt;
&lt;li&gt;如果size &amp;gt;=nb + 0x20&lt;ol&gt;
&lt;li&gt;切割top chunk&lt;/li&gt;
&lt;li&gt;设置victim的头部信息，设置remainder的头部信息，为remainder的下一个堆块设置prev_size&lt;/li&gt;
&lt;li&gt;返回用户堆块&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;否则 ，查看fastbin是否存在堆块&lt;ol&gt;
&lt;li&gt;若存在进行malloc_consolidate，让fastbin堆块合并放入unsorted bin&lt;/li&gt;
&lt;li&gt;如果nb属于smallbin的范围，获取对应smallbin的idx&lt;/li&gt;
&lt;li&gt;如果nb属于largebin的范围，获取对应larrgebin的idx&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;否则&lt;ol&gt;
&lt;li&gt;就是前面都不能找到合适堆块&lt;/li&gt;
&lt;li&gt;直接sysmalloc&lt;/li&gt;
&lt;li&gt;返回用户堆块&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
 &lt;blockquote&gt;This rendering was automatically generated by Frosti Feed and may have formatting issues. For the best experience, please visit: &lt;a href=&quot;https://c4e-i-um-github-io.vercel.app/blog/glibc239-malloc%E6%80%BB%E7%BB%93/&quot;&gt;https://c4e-i-um-github-io.vercel.app/blog/glibc239-malloc%E6%80%BB%E7%BB%93/&lt;/a&gt;&lt;/blockquote&gt;</content:encoded><dc:creator>Caelum</dc:creator><pubDate>Sat, 01 Nov 2025 00:00:00 GMT</pubDate></item><item><title>浅析tcache bin的前世今生</title><link>https://c4e-i-um-github-io.vercel.app/blog/%E6%B5%85%E6%9E%90tcache-bin%E7%9A%84%E5%89%8D%E4%B8%96%E4%BB%8A%E7%94%9F/</link><guid isPermaLink="true">https://c4e-i-um-github-io.vercel.app/blog/%E6%B5%85%E6%9E%90tcache-bin%E7%9A%84%E5%89%8D%E4%B8%96%E4%BB%8A%E7%94%9F/</guid><description>tcache的传奇一生</description><content:encoded>&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;本文重点关注两个方面，tcache的取和放，意在帮助自己更加深入理解各个版本的tcache的行为，提高ctf做题能力。&lt;/p&gt;
&lt;h2&gt;tcahe的分水岭&lt;/h2&gt;
&lt;p&gt;2.26 tcache出现
2.28 _int_free引入key防止双重释放
2.32  PROTECT_PTR的引入
2.34  使得key随机生成，而非tcache_perthread_struct的地址&lt;/p&gt;
&lt;h1&gt;tcache机制的演变以及对应的绕过手法&lt;/h1&gt;
&lt;h2&gt;glibc 2.26-2.27&lt;/h2&gt;
&lt;h3&gt;tcache相关的宏定义&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#if USE_TCACHE
/* We want 64 entries.  This is an arbitrary limit, which tunables can reduce.  */
# define TCACHE_MAX_BINS		64
# define MAX_TCACHE_SIZE	tidx2usize (TCACHE_MAX_BINS-1)

/* Only used to pre-fill the tunables.  */
# define tidx2usize(idx)	(((size_t) idx) * MALLOC_ALIGNMENT + MINSIZE - SIZE_SZ)

/* When &amp;quot;x&amp;quot; is from chunksize().  */
# define csize2tidx(x) (((x) - MINSIZE + MALLOC_ALIGNMENT - 1) / MALLOC_ALIGNMENT)
/* When &amp;quot;x&amp;quot; is a user-provided size.  */
# define usize2tidx(x) csize2tidx (request2size (x))

/* With rounding and alignment, the bins are...
   idx 0   bytes 0..24 (64-bit) or 0..12 (32-bit)
   idx 1   bytes 25..40 or 13..20
   idx 2   bytes 41..56 or 21..28
   etc.  */

/* This is another arbitrary limit, which tunables can change.  Each
   tcache bin will hold at most this number of chunks.  */
# define TCACHE_FILL_COUNT 7
#endif
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;tcache相关的结构体&lt;/h3&gt;
&lt;p&gt;malloc_par里有与tcache相关的内容&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct malloc_par
{
  /* Tunable parameters */
  unsigned long trim_threshold;
  INTERNAL_SIZE_T top_pad;
  INTERNAL_SIZE_T mmap_threshold;
  INTERNAL_SIZE_T arena_test;
  INTERNAL_SIZE_T arena_max;

  /* Memory map support */
  int n_mmaps;
  int n_mmaps_max;
  int max_n_mmaps;
  /* the mmap_threshold is dynamic, until the user sets
     it manually, at which point we need to disable any
     dynamic behavior. */
  int no_dyn_threshold;

  /* Statistics */
  INTERNAL_SIZE_T mmapped_mem;
  INTERNAL_SIZE_T max_mmapped_mem;

  /* First address handed out by MORECORE/sbrk.  */
  char *sbrk_base;

//下面是tcache相关的内容
---------------------------------------------------------------------


#if USE_TCACHE
  /* Maximum number of buckets to use.  */
  size_t tcache_bins;
  size_t tcache_max_bytes;
  /* Maximum number of chunks in each bucket.  */
  size_t tcache_count;
  /* Maximum number of chunks to remove from the unsorted list, which
     aren&amp;#39;t used to prefill the cache.  */
  size_t tcache_unsorted_limit;
#endif
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到这里定义了malloc_par结构体名为mp_&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;
static struct malloc_par mp_ =
{
  .top_pad = DEFAULT_TOP_PAD,
  .n_mmaps_max = DEFAULT_MMAP_MAX,
  .mmap_threshold = DEFAULT_MMAP_THRESHOLD,
  .trim_threshold = DEFAULT_TRIM_THRESHOLD,
#define NARENAS_FROM_NCORES(n) ((n) * (sizeof (long) == 4 ? 2 : 8))
  .arena_test = NARENAS_FROM_NCORES (1)
#if USE_TCACHE
  ,
  .tcache_count = TCACHE_FILL_COUNT,
  .tcache_bins = TCACHE_MAX_BINS,
  .tcache_max_bytes = tidx2usize (TCACHE_MAX_BINS-1),
  .tcache_unsorted_limit = 0 /* No limit.  */
#endif
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;tcache_entry&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#if USE_TCACHE

/* We overlay this structure on the user-data portion of a chunk when
   the chunk is stored in the per-thread cache.  */
typedef struct tcache_entry
{
  struct tcache_entry *next;
} tcache_entry;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;tcache_perthread_struct&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct tcache_perthread_struct
{
  char counts[TCACHE_MAX_BINS];
  tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MAX_TCACHE_COUNT以及一些其他的东西&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define MAX_TCACHE_COUNT 127	/* Maximum value of counts[] entries.  */

static __thread bool tcache_shutting_down = false;
static __thread tcache_perthread_struct *tcache = NULL;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;tcache主要行为&lt;/h3&gt;
&lt;h4&gt;放入tcache bin的时候&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;/* Caller must ensure that we know tc_idx is valid and there&amp;#39;s room
   for more chunks.  */
tatic __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
  assert (tc_idx &amp;lt; TCACHE_MAX_BINS);
  e-&amp;gt;next = tcache-&amp;gt;entries[tc_idx];   //这里采用的是头插法
  tcache-&amp;gt;entries[tc_idx] = e;
  ++(tcache-&amp;gt;counts[tc_idx]);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;申请的时候&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;static __always_inline void *
tcache_get (size_t tc_idx)
{
  tcache_entry *e = tcache-&amp;gt;entries[tc_idx];
  assert (tc_idx &amp;lt; TCACHE_MAX_BINS);
  assert (tcache-&amp;gt;entries[tc_idx] &amp;gt; 0);
  tcache-&amp;gt;entries[tc_idx] = e-&amp;gt;next;
  --(tcache-&amp;gt;counts[tc_idx]);
  return (void *) e;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;p&gt;这个时期的tcache存在着很大的漏洞，几乎没有防护，也为后来打patch埋下基础&lt;/p&gt;
&lt;h2&gt;glibc 2.28-2.33&lt;/h2&gt;
&lt;h3&gt;tcache 相关定义&lt;/h3&gt;
&lt;p&gt;这里只展示不同之处&lt;/p&gt;
&lt;h3&gt;tcache_entry&lt;/h3&gt;
&lt;p&gt;可以看到相比于上一个阶段，这里多了个key指针，类型是struct tcache_perthread_struct *,指向的是当前线程的struct tcache_entry结构体变量，用于检测是否出现了double free&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#if USE_TCACHE

/* We overlay this structure on the user-data portion of a chunk when
   the chunk is stored in the per-thread cache.  */
typedef struct tcache_entry
{
  struct tcache_entry *next;
  /* This field exists to detect double frees.  */
  struct tcache_perthread_struct *key;
} tcache_entry;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;tcache_perthread_struct&lt;/h3&gt;
&lt;p&gt;可以看到原来的char counts[TCACHE_MAX_BINS]变成了uint16_t类型，也许是预防了一手类型混淆，劫持  tcache_perthread_struct结构体的时候就需要注意了&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct tcache_perthread_struct
{
  uint16_t counts[TCACHE_MAX_BINS];  
  tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;tcache的行为&lt;/h3&gt;
&lt;h4&gt;放入tcache bin&lt;/h4&gt;
&lt;p&gt;可以看到多了个PROTECT_PTR，相关宏定义,也就是放入时候的堆块的 &lt;code&gt;e的next指针的地址&amp;gt;&amp;gt;12&lt;/code&gt; 与 &lt;code&gt;tcache-&amp;gt;entries\[tc_idx\]&lt;/code&gt; 异或，也就是和当前堆块所属下标的tcache-&amp;gt;entries指向的第一个堆块，根据头插法，其实就是和要释放堆块同属一个entries的上一个堆块异或（其实就是next指针的值）
简单来说就是&lt;code&gt;e的next指针的取地址&amp;gt;&amp;gt;12&lt;/code&gt;和 &lt;code&gt;e的next指针的值&lt;/code&gt;异或了&lt;/p&gt;
&lt;p&gt;不过当第一个堆块放入的时候tcache-&amp;gt;entries指向的第一个堆块为NULL, 也就是0,相当于没有xor,可以用来泄露堆地址&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define PROTECT_PTR(pos, ptr) \
  ((__typeof (ptr)) ((((size_t) pos) &amp;gt;&amp;gt; 12) ^ ((size_t) ptr)))
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);

  /* Mark this chunk as &amp;quot;in the tcache&amp;quot; so the test in _int_free will
     detect a double free.  */
  e-&amp;gt;key = tcache;

  e-&amp;gt;next = PROTECT_PTR (&amp;amp;e-&amp;gt;next, tcache-&amp;gt;entries[tc_idx]);
  tcache-&amp;gt;entries[tc_idx] = e;
  ++(tcache-&amp;gt;counts[tc_idx]);
}
/* Caller must ensure that we know tc_idx is valid and there&amp;#39;s
   available chunks to remove.  */
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;申请&lt;/h4&gt;
&lt;p&gt;可以看见多了个REVEAL_PTR，其实根据xor的性质，直接还原了&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define REVEAL_PTR(ptr)  PROTECT_PTR (&amp;amp;ptr, ptr)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;``&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;static __always_inline void *
tcache_get (size_t tc_idx)
{
  tcache_entry *e = tcache-&amp;gt;entries[tc_idx];
  if (__glibc_unlikely (!aligned_OK (e)))
    malloc_printerr (&amp;quot;malloc(): unaligned tcache chunk detected&amp;quot;);
  tcache-&amp;gt;entries[tc_idx] = REVEAL_PTR (e-&amp;gt;next);
  --(tcache-&amp;gt;counts[tc_idx]);
  e-&amp;gt;key = NULL;
  return (void *) e;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;释放的时候&lt;/h4&gt;
&lt;p&gt;可以看到  &lt;code&gt;if (__glibc_unlikely (e-&amp;gt;key == tcache)) &lt;/code&gt; 如果相等就会遍历 处于相同下标 entrys的所有chunk, 来检查要释放的堆块是否已经存在，是否double free&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;static void
_int_free (mstate av, mchunkptr p, int have_lock)
{
  INTERNAL_SIZE_T size;        /* its size */
  mfastbinptr *fb;             /* associated fastbin */
  mchunkptr nextchunk;         /* next contiguous chunk */
  INTERNAL_SIZE_T nextsize;    /* its size */
  int nextinuse;               /* true if nextchunk is used */
  INTERNAL_SIZE_T prevsize;    /* size of previous contiguous chunk */
  mchunkptr bck;               /* misc temp for linking */
  mchunkptr fwd;               /* misc temp for linking */

  size = chunksize (p);

  /* Little security check which won&amp;#39;t hurt performance: the
     allocator never wrapps around at the end of the address space.
     Therefore we can exclude some size values which might appear
     here by accident or by &amp;quot;design&amp;quot; from some intruder.  */
  if (__builtin_expect ((uintptr_t) p &amp;gt; (uintptr_t) -size, 0)
      || __builtin_expect (misaligned_chunk (p), 0))
    malloc_printerr (&amp;quot;free(): invalid pointer&amp;quot;);
  /* We know that each chunk is at least MINSIZE bytes in size or a
     multiple of MALLOC_ALIGNMENT.  */
  if (__glibc_unlikely (size &amp;lt; MINSIZE || !aligned_OK (size)))
    malloc_printerr (&amp;quot;free(): invalid size&amp;quot;);

  check_inuse_chunk(av, p);

#if USE_TCACHE
  {
    size_t tc_idx = csize2tidx (size);
    if (tcache != NULL &amp;amp;&amp;amp; tc_idx &amp;lt; mp_.tcache_bins)
      {
	/* Check to see if it&amp;#39;s already in the tcache.  */
	tcache_entry *e = (tcache_entry *) chunk2mem (p);

	/* This test succeeds on double free.  However, we don&amp;#39;t 100%
	   trust it (it also matches random payload data at a 1 in
	   2^&amp;lt;size_t&amp;gt; chance), so verify it&amp;#39;s not an unlikely
	   coincidence before aborting.  */
	if (__glibc_unlikely (e-&amp;gt;key == tcache))
	  {
	    tcache_entry *tmp;
	    size_t cnt = 0;
	    LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
	    for (tmp = tcache-&amp;gt;entries[tc_idx];
		 tmp;
		 tmp = REVEAL_PTR (tmp-&amp;gt;next), ++cnt)
	      {
		if (cnt &amp;gt;= mp_.tcache_count)
		  malloc_printerr (&amp;quot;free(): too many chunks detected in tcache&amp;quot;);
		if (__glibc_unlikely (!aligned_OK (tmp)))
		  malloc_printerr (&amp;quot;free(): unaligned chunk detected in tcache 2&amp;quot;);
		if (tmp == e)
		  malloc_printerr (&amp;quot;free(): double free detected in tcache 2&amp;quot;);
		/* If we get here, it was a coincidence.  We&amp;#39;ve wasted a
		   few cycles, but don&amp;#39;t abort.  */
	      }
	  }

	if (tcache-&amp;gt;counts[tc_idx] &amp;lt; mp_.tcache_count)
	  {
	    tcache_put (p, tc_idx);
	    return;
	  }
      }
  }
#endif
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;glibc2.34--至今&lt;/h2&gt;
&lt;p&gt;主要不同只有一个地方，原来e-&amp;gt;key=tcache现在e-&amp;gt;key=tcache_key&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);

  /* Mark this chunk as &amp;quot;in the tcache&amp;quot; so the test in _int_free will
     detect a double free.  */
  e-&amp;gt;key = tcache_key;

  e-&amp;gt;next = PROTECT_PTR (&amp;amp;e-&amp;gt;next, tcache-&amp;gt;entries[tc_idx]);
  tcache-&amp;gt;entries[tc_idx] = e;
  ++(tcache-&amp;gt;counts[tc_idx]);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么tcache_key哪来的呢&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;static void
tcache_key_initialize (void)
{
  if (__getrandom (&amp;amp;tcache_key, sizeof(tcache_key), GRND_NONBLOCK)
      != sizeof (tcache_key))
    {
      tcache_key = random_bits ();
#if __WORDSIZE == 64
      tcache_key = (tcache_key &amp;lt;&amp;lt; 32) | random_bits ();
#endif
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实际上是tcache_key变为了一个随机数&lt;/p&gt;
&lt;h3&gt;释放的时候&lt;/h3&gt;
&lt;p&gt;可以看到if(e-&amp;gt;key == tcache)变为了 if(e-&amp;gt;key == tcache_key),但我们只要能获取key一样可以绕过检测&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;static void
_int_free (mstate av, mchunkptr p, int have_lock)
{
  INTERNAL_SIZE_T size;        /* its size */
  mfastbinptr *fb;             /* associated fastbin */
  mchunkptr nextchunk;         /* next contiguous chunk */
  INTERNAL_SIZE_T nextsize;    /* its size */
  int nextinuse;               /* true if nextchunk is used */
  INTERNAL_SIZE_T prevsize;    /* size of previous contiguous chunk */
  mchunkptr bck;               /* misc temp for linking */
  mchunkptr fwd;               /* misc temp for linking */

  size = chunksize (p);

  /* Little security check which won&amp;#39;t hurt performance: the
     allocator never wrapps around at the end of the address space.
     Therefore we can exclude some size values which might appear
     here by accident or by &amp;quot;design&amp;quot; from some intruder.  */
  if (__builtin_expect ((uintptr_t) p &amp;gt; (uintptr_t) -size, 0)
      || __builtin_expect (misaligned_chunk (p), 0))
    malloc_printerr (&amp;quot;free(): invalid pointer&amp;quot;);
  /* We know that each chunk is at least MINSIZE bytes in size or a
     multiple of MALLOC_ALIGNMENT.  */
  if (__glibc_unlikely (size &amp;lt; MINSIZE || !aligned_OK (size)))
    malloc_printerr (&amp;quot;free(): invalid size&amp;quot;);

  check_inuse_chunk(av, p);

#if USE_TCACHE
  {
    size_t tc_idx = csize2tidx (size);
    if (tcache != NULL &amp;amp;&amp;amp; tc_idx &amp;lt; mp_.tcache_bins)
      {
	/* Check to see if it&amp;#39;s already in the tcache.  */
	tcache_entry *e = (tcache_entry *) chunk2mem (p);

	/* This test succeeds on double free.  However, we don&amp;#39;t 100%
	   trust it (it also matches random payload data at a 1 in
	   2^&amp;lt;size_t&amp;gt; chance), so verify it&amp;#39;s not an unlikely
	   coincidence before aborting.  */
	if (__glibc_unlikely (e-&amp;gt;key == tcache_key))
	  {
	    tcache_entry *tmp;
	    size_t cnt = 0;
	    LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
	    for (tmp = tcache-&amp;gt;entries[tc_idx];
		 tmp;
		 tmp = REVEAL_PTR (tmp-&amp;gt;next), ++cnt)
	      {
		if (cnt &amp;gt;= mp_.tcache_count)
		  malloc_printerr (&amp;quot;free(): too many chunks detected in tcache&amp;quot;);
		if (__glibc_unlikely (!aligned_OK (tmp)))
		  malloc_printerr (&amp;quot;free(): unaligned chunk detected in tcache 2&amp;quot;);
		if (tmp == e)
		  malloc_printerr (&amp;quot;free(): double free detected in tcache 2&amp;quot;);
		/* If we get here, it was a coincidence.  We&amp;#39;ve wasted a
		   few cycles, but don&amp;#39;t abort.  */
	      }
	  }

	if (tcache-&amp;gt;counts[tc_idx] &amp;lt; mp_.tcache_count)
	  {
	    tcache_put (p, tc_idx);
	    return;
	  }
      }
  }
#endif
&lt;/code&gt;&lt;/pre&gt;
 &lt;blockquote&gt;This rendering was automatically generated by Frosti Feed and may have formatting issues. For the best experience, please visit: &lt;a href=&quot;https://c4e-i-um-github-io.vercel.app/blog/%E6%B5%85%E6%9E%90tcache-bin%E7%9A%84%E5%89%8D%E4%B8%96%E4%BB%8A%E7%94%9F/&quot;&gt;https://c4e-i-um-github-io.vercel.app/blog/%E6%B5%85%E6%9E%90tcache-bin%E7%9A%84%E5%89%8D%E4%B8%96%E4%BB%8A%E7%94%9F/&lt;/a&gt;&lt;/blockquote&gt;</content:encoded><dc:creator>Caelum</dc:creator><pubDate>Mon, 01 Sep 2025 00:00:00 GMT</pubDate></item><item><title>堆泄漏技巧</title><link>https://c4e-i-um-github-io.vercel.app/blog/%E5%A0%86%E6%B3%84%E6%BC%8F%E6%8A%80%E5%B7%A7/</link><guid isPermaLink="true">https://c4e-i-um-github-io.vercel.app/blog/%E5%A0%86%E6%B3%84%E6%BC%8F%E6%8A%80%E5%B7%A7/</guid><description>堆泄漏技巧</description><content:encoded>&lt;h1&gt;有UAF&lt;/h1&gt;
&lt;p&gt;有UAF，基本就不用说了，随便一种就行:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;利用tcache/fastbin&lt;ol&gt;
&lt;li&gt;利用释放后的tcache next指针&amp;gt;&amp;gt;12，可获取堆地址&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;利用unsorted bin&lt;ol&gt;
&lt;li&gt;利用释放后的unsorted bin的fd和bk , 可获取libc地址&lt;/li&gt;
&lt;li&gt;释放两个堆块(这两个堆块不连续，否则会合并)，让他们进入unsorted bin，可同时获取libc和堆地址，&lt;strong&gt;前提是输出不被截断&lt;/strong&gt;
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/heap/heap-leak-techniques/Pasted%20image%2020260331110336.png&quot; alt=&quot;Pasted image 20260331110336&quot;&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;无UAF,但是输出不截断&lt;/h1&gt;
&lt;p&gt;主要是利用了堆块申请出来不会主动清空fd和bk 还有fd_nextsize和bk_nextsize&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;利用unsorted bin&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;add(0,0x418)
add(1,0x10)
add(2,0x4f8)
free(0)
free(2)
add(3,0x418)
show(3)   //不截断输出
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;利用效果也是这样
&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/heap/heap-leak-techniques/Pasted%20image%2020260331110658.png&quot; alt=&quot;Pasted image 20260331110658&quot;&gt;
2. 利用large bin&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;add(0,0x418)
add(1,0x10)
free(0)
add(2,0x4f8)
add(3,0x418)
show(3)    //不截断输出
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://c4e-i-um-github-io.vercel.app/image/pwn/heap/heap-leak-techniques/Pasted%20image%2020260331111257.png&quot; alt=&quot;Pasted image 20260331111257&quot;&gt;&lt;/p&gt;
 &lt;blockquote&gt;This rendering was automatically generated by Frosti Feed and may have formatting issues. For the best experience, please visit: &lt;a href=&quot;https://c4e-i-um-github-io.vercel.app/blog/%E5%A0%86%E6%B3%84%E6%BC%8F%E6%8A%80%E5%B7%A7/&quot;&gt;https://c4e-i-um-github-io.vercel.app/blog/%E5%A0%86%E6%B3%84%E6%BC%8F%E6%8A%80%E5%B7%A7/&lt;/a&gt;&lt;/blockquote&gt;</content:encoded><dc:creator>Caelum</dc:creator><pubDate>Mon, 31 Mar 2025 00:00:00 GMT</pubDate></item></channel></rss>