sprintf 段错误

最近迁移代码测试功能的时候发现一个段错误,打印堆栈显示std::string析构时段错误。最后分析原因:sprintf造成字符数组内存溢出,踩内存,导致附近std::string对象被改修,析构时产生段错误。

原代码

if(response_code == 200)
{
	sprintf(temp,"HTTP请求成功[%s]", strResponce.c_str());
	dispPlayMsg("HTTP请求",index,temp,WEB_LOG);
	
	return TRUE;
}

strResponce为http的响应结果,平时的都是本公司的soap服务,返回
字节数都很少,测试时的服务返回长度超过temp长度,溢出,踩内存。

修改后

if(response_code == 200)
{
	//sprintf易造成越界,导致段错误
	snprintf(temp, sizeof(temp), "HTTP请求成功[%s]", strResponce.c_str());
	dispPlayMsg("HTTP请求",index,temp,WEB_LOG);

	return TRUE;
}

慎用sprintf,尽量使用snprintf。
原来工作中就遇到过sprintf造成的段错误。

发表在 C/C++ | 标签为 , | 留下评论

printf/sprintf/NULL 空指针问题

最近从windows到linux 迁移代码,遇到了一个sprintf参数空指针段错误。

很疑惑,按说项目这么久不该出现段错误。

1.代码

原代码(用到msxml)


sprintf(pItemData.szNodeText,"%s",(const char*)nodeptr->Gettext());

移植后的代码(用到libxml2)


xmlCharTemp = xmlNodeGetContent(xmlChildNode);

sprintf(pItemData.szNodeText,"%s", (const char*)xmlCharTemp);

xmlFree(xmlCharTemp);

用什么xml解析库并不关键,关键是sprintf的参数都有可能是空指针。

于是乎实验了一下,发现vc++6.0 和 g++编译器存在差异性。

 

 

Windows-vc++6.0-sprintf截图

 

 

Linux-g++sprintf-截图

2.G++为什么会出现这种情况?

输出汇编代码


g++ test.cpp -S -o test.S


 .file "test.cpp"
.section .rodata
.LC0:
.string "%s"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movq $0, -8(%rbp)
leaq -32(%rbp), %rax
movl $0, %edx
movl $.LC0, %esi
movq %rax, %rdi
movl $0, %eax
call sprintf
leaq -32(%rbp), %rax
movq %rax, %rdi
call puts
movq -8(%rbp), %rdx
leaq -32(%rbp), %rax
movq %rdx, %rsi
movq %rax, %rdi
call strcpy
leaq -32(%rbp), %rax
movq %rax, %rdi
call puts
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-28)"
.section .note.GNU-stack,"",@progbits


会发现两次调用sprintf,第一次调用的call sprintf,第二次调用的call strcpy。

3.printf也会存在类似的段错误

Windows-vc++6.0-printf截图

 

Linux-g++-printf-截图

 

g++汇编代码


.file "test2.cpp"
.section .rodata
.LC0:
.string "%s"
.LC1:
.string "%s \n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
movq $0, -8(%rbp)
movq -8(%rbp), %rax
movq %rax, %rsi
movl $.LC0, %edi
movl $0, %eax
call printf
movq -8(%rbp), %rax
movq %rax, %rsi
movl $.LC1, %edi
movl $0, %eax
call printf
movq -8(%rbp), %rax
movq %rax, %rdi
call puts
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-28)"
.section .note.GNU-stack,"",@progbits

会发现前两次调用的call printf,而第三次调用call puts。

 

4.总结

尽量不要期待编译器去做空指针的判断优化,因为大多数情况下你不知道编译器是怎么实现的,况且编译器间存在差距,自己保证参数的不为空指针。

 

最后代码无奈改成了这个样子。


sprintf(pItemData.szNodeText,"%s", xmlCharTemp!=NULL?(const char*)xmlCharTemp:"(null)");

参考:https://stackoverflow.com/questions/11589342/what-is-the-behavior-of-printing-null-with-printfs-s-specifier

发表在 C/C++ | 标签为 , , | 留下评论

编译链接“ undefined reference to ”

最近在移植一个项目,从windows移植到Linux,在解决掉编译前期问题后,在链接的时候碰到了各种的“ undefined reference to ”问题。

检查所依赖的.h文件是否 #include <>

首先检查所依赖的.h文件是否 #include“xxx.h”

链接时缺少了相关的目标文件(.o)或者库文件(.so)

造成这种情况的原因比较多:

忘记指定目标文件(.o);

忘记指定链接库(-L /xxx/yyy/  -lA);

遗漏指定链接库(B动态库依赖A动态库,链接忘记指定A动态库 -lA)

fun.h

#ifndef __FUN_H__
#define __FUN_H__

int fun();

#endif

fun.cpp

#include "fun.h"
int fun()
{
	return 0;
}

fun1.h

#ifndef __FUN1_H__
#define __FUN1_H__

int fun1();

#endif

fun1.cpp

#include "fun.h"

int fun1()
{
	return fun();
}

缺少链接库

root@localhost:~/test# g++ -fpic -shared fun.cpp -o libfun.so
root@localhost:~/test# g++ -fpic -shared fun1.cpp -o libfun1.so
root@localhost:~/test# g++ main.cpp -o main -L ./ -I ./ -lfun1
.//libfun1.so: undefined reference to `fun()'
collect2: error: ld returned 1 exit status

正确的编译

root@localhost:~/test# g++ -fpic -shared fun.cpp -o libfun.so
root@localhost:~/test# g++ -fpic -shared fun1.cpp -o libfun1.so
root@localhost:~/test# g++ main.cpp -o main -L ./ -I ./ -lfun1 -lfun
root@localhost:~/test#

继续阅读

发表在 编译 | 标签为 , , | 留下评论

网络摄像头 弱口令扫描

确定想扫描的IP范围

首先要确定要扫描的ip范围,可以自己定手写,我是按地域从网站上爬取的。

#coding:utf-8
import requests
from bs4 import BeautifulSoup
from urllib import quote
import re
place_name = "日本"
url_ = 'http://ip.yqie.com/search.aspx?searchword=' + quote(place_name) + "&pagecurrent="
pagecount = re.findall('页码:1/(\d*?)<',requests.get(url_+'1').content)
index = 1
fp = open('ip_range.txt','a')
while index < int(pagecount[0]):
    url = url_ + str(index)
    page = requests.get(url)
    soup = BeautifulSoup(page.content,'lxml').find_all("tr")
    j = 0
    for i in soup:
        if j == 0:
            j = 1
            continue
        fp.write(i.contents[1].contents[0]  + '    ' + i.contents[3].contents[0] + '\n')
    index = index + 1
fp.close()

生成以下格式的文本:
171.105.32.0 171.105.33.255
171.105.34.0 171.105.35.255
171.105.36.0 171.105.36.255
171.105.37.0 171.105.38.255
171.105.39.0 171.105.79.255

继续阅读

发表在 杂项 | 标签为 , , | 留下评论

wordpress 恶意访问

在vps上抓包分析telegram协议时,意外发现有人尝试登录我的wordpress后台,很是意外,看来安全意识不能没有啊。

然后我装了个wordpress插件simple login log来记录登录日志,实验发现并不能记录登录失败的日志,所以看了下simple login log插件源码,simple-login-log.php文件中有以下代码行,

if( isset($this->opt['failed_attempts']) )

因为对php语言不是很了解,也没太仔细看opt在哪初始化或者修改的,索性直接把以上代码全替换成

if( 1 )

估计simple login log作者这么做是怕有攻击者对你网站实施密码爆破,使得mysql数据库爆掉。

而后就能看到登录失败的日志了。

仔细观察一下时间,你会发现这些人还蛮专业,隔两个小时尝试一次,怕短时间内多次访问被禁掉。

有一些插件可以做一些防护,比如:WP Limit Login Attempts、WP-Ban等,但考虑到本来vps内存cpu呀就不够用的,我就没安装这些插件了黑客还是蛮多的,不过我这小站也没啥,任他们去吧

 

发表在 wordpress | 标签为 | 留下评论

Android QQ/微信 数据库

QQ和微信数据本地数据库,可以获取聊天信息以及好友信息等。

QQ

安卓手机QQ数据库:/data/data/com.tencent.qq/databases/QQ号.db(手机root后可获取 RE浏览器);
数据库文件为sqlite数据库;
私聊天记录放在表:mr_friend_MD5(QQ号)_New
群聊天记录放在表:mr_troop_MD5(群号)_New

sqlcipher.exe打开数据库发现一些信息乱码,经过加密。加密方法循环异或IMEI号。

解密demo:

# -*- coding: utf-8 -*-
import sqlite3

IMEI="866536022175869"
conn = sqlite3.connect("971774262.db")
cursor = conn.execute("SELECT frienduin,selfuin,senderuin,msgdata  from mr_troop_158C59D128304F55302B275E6427CA1E_New ")
print "select database successfully";
print "群号\t\t己方QQ\t\t发送方QQ\t\t聊天内容"
for row in cursor:
    a= row[0]
    #print a
    sbstr=""
    for i in range(0,len(a)):
        sbstr+=chr(ord(a[i])^ord(IMEI[i%15]))
    sbstr+="\t"
    a= row[1]
    for i in range(0,len(a)):
        sbstr+=chr(ord(a[i])^ord(IMEI[i%15]))
    sbstr+="\t"
    a= row[2]
    for i in range(0,len(a)):
        sbstr+=chr(ord(a[i])^ord(IMEI[i%15]))
    sbstr+="\t"
    a= row[3]
    for i in range(0,len(a)):
        sbstr+=chr(ord(a[i])^ord(IMEI[i%15]))
    print sbstr

微信

安卓微信数据库:/data/data/com.tencent.mm/MicroMsg    EnMicroMsg.db (需要ROOT)

数据库整个加密,可以用sqlcipher.exe软件直接打开,也可以自己写代码。

数据库密码:拼接IMEI和uin,通过md5加密后,取前7位小写的字符串

uin获取:/data/data/com.tencent.mm/shared_prefs/auth_info_key_prefs.xml (需要ROOT)

发表在 杂项 | 标签为 , | 留下评论

leveldb log读写

leveldb在内存存储为Memtable,为防止异常情况Memtable没来得及写入SSTable文件程序挂掉,leveldb首先会写记录进入log文件,再把记录写入Memtable。这样即使异常挂掉也可以从log文件恢复数据。

log相关的代码在

  • db/log_format.h
  • db/log_reader.h
  • db/log_reader.cc
  • db/log_writer.h
  • db/log_writer.cc

1.log文件格式

log_format.h头文件

#ifndef STORAGE_LEVELDB_DB_LOG_FORMAT_H_
#define STORAGE_LEVELDB_DB_LOG_FORMAT_H_

namespace leveldb {
namespace log {

enum RecordType {
  // Zero is reserved for preallocated files
  kZeroType = 0,

  kFullType = 1,

  // For fragments
  kFirstType = 2,
  kMiddleType = 3,
  kLastType = 4
};
static const int kMaxRecordType = kLastType;

static const int kBlockSize = 32768; //32K

// Header is checksum (4 bytes), length (2 bytes), type (1 byte).
static const int kHeaderSize = 4 + 2 + 1;

}  // namespace log
}  // namespace leveldb

#endif  // STORAGE_LEVELDB_DB_LOG_FORMAT_H_

从这头文件可以看出,log文件分块,每一块32768字节(32K),当一条记录过大在1个block装不下时,记录可以分几部分装在不同的block里,每一部分有一个7字节的头(4字节crc校验码+2字节数据长度+1字节type)。

 

为什么拿2字节来表示长度?因为每部分数据不可能超过32K( 2^16 > 32K > 2^8 ),两个字节足够了。

 

下图copy from web

 

2.log的写入逻辑

Status Writer::AddRecord(const Slice& slice) {
  const char* ptr = slice.data();
  size_t left = slice.size();

  // Fragment the record if necessary and emit it.  Note that if slice
  // is empty, we still want to iterate once to emit a single
  // zero-length record
  Status s;
  bool begin = true;
  do {
    const int leftover = kBlockSize - block_offset_;
    assert(leftover >= 0);
    if (leftover < kHeaderSize) {
      // Switch to a new block
      if (leftover > 0) {
        // Fill the trailer (literal below relies on kHeaderSize being 7)
        //当块剩余字节连头都装不下时填充0
        assert(kHeaderSize == 7);
        dest_->Append(Slice("\x00\x00\x00\x00\x00\x00", leftover));
      }
      block_offset_ = 0;
    }

    // Invariant: we never leave < kHeaderSize bytes in a block.
    assert(kBlockSize - block_offset_ - kHeaderSize >= 0);

	//根据标志、长度确定类型:
	//kFullType(整条记录都在可以放到本block),
	//kFirstType(第一部分放到本block)	,kMiddleType...kLastType..
    const size_t avail = kBlockSize - block_offset_ - kHeaderSize;
    const size_t fragment_length = (left < avail) ? left : avail;

    RecordType type;
    const bool end = (left == fragment_length);
    if (begin && end) {
      type = kFullType;
    } else if (begin) {
      type = kFirstType;
    } else if (end) {
      type = kLastType;
    } else {
      type = kMiddleType;
    }

    s = EmitPhysicalRecord(type, ptr, fragment_length);
    ptr += fragment_length;
    left -= fragment_length;
    begin = false;
  } while (s.ok() && left > 0);
  return s;
}

Status Writer::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n) {
  assert(n <= 0xffff);  // Must fit in two bytes
  assert(block_offset_ + kHeaderSize + n <= kBlockSize);

  // Format the header
  char buf[kHeaderSize];
  buf[4] = static_cast<char>(n & 0xff);
  buf[5] = static_cast<char>(n >> 8);
  buf[6] = static_cast<char>(t);

  // Compute the crc of the record type and the payload.
  uint32_t crc = crc32c::Extend(type_crc_[t], ptr, n);
  crc = crc32c::Mask(crc);                 // Adjust for storage
  EncodeFixed32(buf, crc);

  // Write the header and the payload
  Status s = dest_->Append(Slice(buf, kHeaderSize));
  if (s.ok()) {
    s = dest_->Append(Slice(ptr, n));
    if (s.ok()) {
      s = dest_->Flush();
    }
  }
  block_offset_ += kHeaderSize + n;
  return s;
}

循环计算长度、打上Type标签、计算CRC,然后Append到buffer中,再然后Flush到文件中(flush到文件中并不准确,暂且这么一说吧,为什么?见第4小结)。

3.基于POSIX的WritableFile接口实现

env.h是为跨平台准备的env接口,在这里我们只看其posix实现(env_posix.cc)

class PosixWritableFile : public WritableFile {
 private:
  // buf_[0, pos_-1] contains data to be written to fd_.
  std::string filename_;
  int fd_;
  char buf_[kBufSize];
  size_t pos_;

 public:
  PosixWritableFile(const std::string& fname, int fd)
      : filename_(fname), fd_(fd), pos_(0) { }

  ~PosixWritableFile() {
    if (fd_ >= 0) {
      // Ignoring any potential errors
      Close();
    }
  }

  virtual Status Append(const Slice& data) {
    size_t n = data.size();
    const char* p = data.data();

    // Fit as much as possible into buffer.
    size_t copy = std::min(n, kBufSize - pos_);
    memcpy(buf_ + pos_, p, copy);
    p += copy;
    n -= copy;
    pos_ += copy;
    if (n == 0) {
      return Status::OK();
    }

    // Can't fit in buffer, so need to do at least one write.
    Status s = FlushBuffered();
    if (!s.ok()) {
      return s;
    }

    // Small writes go to buffer, large writes are written directly.
    if (n < kBufSize) {
      memcpy(buf_, p, n);
      pos_ = n;
      return Status::OK();
    }
    return WriteRaw(p, n);
  }

  virtual Status Close() {
    Status result = FlushBuffered();
    const int r = close(fd_);
    if (r < 0 && result.ok()) {
      result = PosixError(filename_, errno);
    }
    fd_ = -1;
    return result;
  }

  virtual Status Flush() {
    return FlushBuffered();
  }

  Status SyncDirIfManifest() {
    const char* f = filename_.c_str();
    const char* sep = strrchr(f, '/');
    Slice basename;
    std::string dir;
    if (sep == nullptr) {
      dir = ".";
      basename = f;
    } else {
      dir = std::string(f, sep - f);
      basename = sep + 1;
    }
    Status s;
    if (basename.starts_with("MANIFEST")) {
      int fd = open(dir.c_str(), O_RDONLY);
      if (fd < 0) {
        s = PosixError(dir, errno);
      } else {
        if (fsync(fd) < 0) {
          s = PosixError(dir, errno);
        }
        close(fd);
      }
    }
    return s;
  }

  virtual Status Sync() {
    // Ensure new files referred to by the manifest are in the filesystem.
    Status s = SyncDirIfManifest();
    if (!s.ok()) {
      return s;
    }
    s = FlushBuffered();
    if (s.ok()) {
      if (fdatasync(fd_) != 0) {
        s = PosixError(filename_, errno);
      }
    }
    return s;
  }

 private:
  Status FlushBuffered() {
    Status s = WriteRaw(buf_, pos_);
    pos_ = 0;
    return s;
  }

  Status WriteRaw(const char* p, size_t n) {
    while (n > 0) {
      ssize_t r = write(fd_, p, n);
      if (r < 0) {
        if (errno == EINTR) {
          continue;  // Retry
        }
        return PosixError(filename_, errno);
      }
      p += r;
      n -= r;
    }
    return Status::OK();
  }
};

4.值得注意的fsync或fdatasync

在上一小结中有这么一个函数

// HAVE_FDATASYNC is defined in the auto-generated port_config.h, which is
// included by port_stdcxx.h.
#if !HAVE_FDATASYNC
#define fdatasync fsync
#endif  // !HAVE_FDATASYNC

  virtual Status Sync() {
    // Ensure new files referred to by the manifest are in the filesystem.
    Status s = SyncDirIfManifest();
    if (!s.ok()) {
      return s;
    }
    s = FlushBuffered();
    if (s.ok()) {
      if (fdatasync(fd_) != 0) {
        s = PosixError(filename_, errno);
      }
    }
    return s;
  }

若果你熟悉标准C的fopen、fprintf,会知道fprintf是有缓冲区的,刷新这些缓冲区的方法是fflush,确保信息传递到OS,但并不意味着它在磁盘上,它也可以在OS中缓冲。这里的write虽然是系统调用,write后的数据有可能在OS中缓冲,fsync或者fdatasync 会确保OS缓冲区中的内容写入物理磁盘。

你也许会在其他日志记录库中看到此类操作:

fprintf (myFileHandle, "something\n");  // output it
fflush (myFileHandle);                  // flush to OS
fsync (fileno (myFileHandle));          // flush to disk

fileno是一个函数,可以得到FILE*文件句柄的基础文件描述符,fsync在描述符上执行确保数据刷到磁盘。

fsync是相对昂贵的操作,因为磁盘写入通常比内存写入慢得多。

可以看一下leveldb的注释

// Options that control write operations
struct LEVELDB_EXPORT WriteOptions {
  // If true, the write will be flushed from the operating system
  // buffer cache (by calling WritableFile::Sync()) before the write
  // is considered complete.  If this flag is true, writes will be
  // slower.
  //
  // If this flag is false, and the machine crashes, some recent
  // writes may be lost.  Note that if it is just the process that
  // crashes (i.e., the machine does not reboot), no writes will be
  // lost even if sync==false.
  //
  // In other words, a DB write with sync==false has similar
  // crash semantics as the "write()" system call.  A DB write
  // with sync==true has similar crash semantics to a "write()"
  // system call followed by "fsync()".
  //
  // Default: false
  bool sync;

  WriteOptions()
      : sync(false) {
  }
};

默认情况下,sync的flag为false,后果是有可能会在异常的时候丢失一部分数据;如果为true的话write的时候会变慢。

参考:
https://stackoverflow.com/questions/10371017/fsync-vs-write-system-call
linux 同步IO: sync、fsync与fdatasync

5.log的读取逻辑

bool Reader::ReadRecord(Slice* record, std::string* scratch) {
  if (last_record_offset_ < initial_offset_) {
    if (!SkipToInitialBlock()) {
      return false;
    }
  }

  scratch->clear();
  record->clear();
  bool in_fragmented_record = false;
  // Record offset of the logical record that we're reading
  // 0 is a dummy value to make compilers happy
  uint64_t prospective_record_offset = 0;

  Slice fragment;
  while (true) {
    const unsigned int record_type = ReadPhysicalRecord(&fragment);

    // ReadPhysicalRecord may have only had an empty trailer remaining in its
    // internal buffer. Calculate the offset of the next physical record now
    // that it has returned, properly accounting for its header size.
    uint64_t physical_record_offset =
        end_of_buffer_offset_ - buffer_.size() - kHeaderSize - fragment.size();

    if (resyncing_) {
      if (record_type == kMiddleType) {
        continue;
      } else if (record_type == kLastType) {
        resyncing_ = false;
        continue;
      } else {
        resyncing_ = false;
      }
    }

    switch (record_type) {
      case kFullType:
        if (in_fragmented_record) {
          // Handle bug in earlier versions of log::Writer where
          // it could emit an empty kFirstType record at the tail end
          // of a block followed by a kFullType or kFirstType record
          // at the beginning of the next block.
          if (!scratch->empty()) {
            ReportCorruption(scratch->size(), "partial record without end(1)");
          }
        }
        prospective_record_offset = physical_record_offset;
        scratch->clear();
        *record = fragment;
        last_record_offset_ = prospective_record_offset;
        return true;

      case kFirstType:
        if (in_fragmented_record) {
          // Handle bug in earlier versions of log::Writer where
          // it could emit an empty kFirstType record at the tail end
          // of a block followed by a kFullType or kFirstType record
          // at the beginning of the next block.
          if (!scratch->empty()) {
            ReportCorruption(scratch->size(), "partial record without end(2)");
          }
        }
        prospective_record_offset = physical_record_offset;
        scratch->assign(fragment.data(), fragment.size());
        in_fragmented_record = true;
        break;

      case kMiddleType:
        if (!in_fragmented_record) {
          ReportCorruption(fragment.size(),
                           "missing start of fragmented record(1)");
        } else {
          scratch->append(fragment.data(), fragment.size());
        }
        break;

      case kLastType:
        if (!in_fragmented_record) {
          ReportCorruption(fragment.size(),
                           "missing start of fragmented record(2)");
        } else {
          scratch->append(fragment.data(), fragment.size());
          *record = Slice(*scratch);
          last_record_offset_ = prospective_record_offset;
          return true;
        }
        break;

      case kEof:
        if (in_fragmented_record) {
          // This can be caused by the writer dying immediately after
          // writing a physical record but before completing the next; don't
          // treat it as a corruption, just ignore the entire logical record.
          scratch->clear();
        }
        return false;

      case kBadRecord:
        if (in_fragmented_record) {
          ReportCorruption(scratch->size(), "error in middle of record");
          in_fragmented_record = false;
          scratch->clear();
        }
        break;

      default: {
        char buf[40];
        snprintf(buf, sizeof(buf), "unknown record type %u", record_type);
        ReportCorruption(
            (fragment.size() + (in_fragmented_record ? scratch->size() : 0)),
            buf);
        in_fragmented_record = false;
        scratch->clear();
        break;
      }
    }
  }
  return false;
}

unsigned int Reader::ReadPhysicalRecord(Slice* result) {
  while (true) {
    if (buffer_.size() < kHeaderSize) {
      if (!eof_) {
        // Last read was a full read, so this is a trailer to skip
        buffer_.clear();
		
		//从文件读one block,放入backing_store_
		//buffer_只是记录backing_store_的读取位置和所剩长度;
		//ReadPhysicalRecord的调用者ReadRecord相当于循环从backing_store_读记录,
		//当buffer_.size() < kHeaderSize 7时,就从实体文件读一个block(32K)
        Status status = file_->Read(kBlockSize, &buffer_, backing_store_);
        end_of_buffer_offset_ += buffer_.size();
        if (!status.ok()) {
          buffer_.clear();
          ReportDrop(kBlockSize, status);
          eof_ = true;
          return kEof;
        } else if (buffer_.size() < kBlockSize) {
          eof_ = true;
        }
        continue;
      } else {
        // Note that if buffer_ is non-empty, we have a truncated header at the
        // end of the file, which can be caused by the writer crashing in the
        // middle of writing the header. Instead of considering this an error,
        // just report EOF.
        buffer_.clear();
        return kEof;
      }
    }

    // Parse the header
    const char* header = buffer_.data();
    const uint32_t a = static_cast<uint32_t>(header[4]) & 0xff;
    const uint32_t b = static_cast<uint32_t>(header[5]) & 0xff;
    const unsigned int type = header[6];
    const uint32_t length = a | (b << 8);
    if (kHeaderSize + length > buffer_.size()) {
      size_t drop_size = buffer_.size();
      buffer_.clear();
      if (!eof_) {
        ReportCorruption(drop_size, "bad record length");
        return kBadRecord;
      }
      // If the end of the file has been reached without reading |length| bytes
      // of payload, assume the writer died in the middle of writing the record.
      // Don't report a corruption.
      return kEof;
    }

    if (type == kZeroType && length == 0) {
      // Skip zero length record without reporting any drops since
      // such records are produced by the mmap based writing code in
      // env_posix.cc that preallocates file regions.
      buffer_.clear();
      return kBadRecord;
    }

    // Check crc
    if (checksum_) {
      uint32_t expected_crc = crc32c::Unmask(DecodeFixed32(header));
      uint32_t actual_crc = crc32c::Value(header + 6, 1 + length);
      if (actual_crc != expected_crc) {
        // Drop the rest of the buffer since "length" itself may have
        // been corrupted and if we trust it, we could find some
        // fragment of a real log record that just happens to look
        // like a valid log record.
        size_t drop_size = buffer_.size();
        buffer_.clear();
        ReportCorruption(drop_size, "checksum mismatch");
        return kBadRecord;
      }
    }

    buffer_.remove_prefix(kHeaderSize + length);

    // Skip physical record that started before initial_offset_
    if (end_of_buffer_offset_ - buffer_.size() - kHeaderSize - length <
        initial_offset_) {
      result->clear();
      return kBadRecord;
    }

    *result = Slice(header + kHeaderSize, length);
    return type;
  }
}

ReadPhysicalRecord的调用者ReadRecord相当于循环从backing_store_读记录到scratch,buffer_记录backing_store_的读取位置和所剩长度,当buffer_.size() < kHeaderSize 7时,就从实体文件读一个block(32K)进backing_store_。其中还有一些CRC校验、类型判断、异常处理等。

注意buffer_(Slice)中没有实际数据,只有指向backing_store_(new char[kBlockSize])数据的指针,Slice的使用者保证在Slice的生命周期内外部数组是有效的。为什么不用直接用std::string,而是最后再把append到std::string里?mybe,每次读32K固定的数据,需要记录的是读取位置和剩余长度,slice和字符数组这样的组合 更适合这个场景。

6.总结

1.理解了log文件的结构,就很容易理解log读写的逻辑。

2.Slice这样看似简单的数据结构,初始自己感觉没有必要存在,仔细想想就会感觉到作者的设计精妙之处。

3.fsync/fdatasync 用或不用,可靠度和性能间取舍。

程序员之路,还是需要积累啊。ヾ(◍°∇°◍)ノ゙

发表在 leveldb | 标签为 , , | 留下评论

leveldb varint 可变长整数

在读leveldb时看到Varint这个东西,瞬间想起了自己做协议分析、XYHY的时光了。GOOGLE的另一个开源项目protobuf也有varint这个东东。许多软件的私有通讯协议也有用到varint。

1.简介

Varint 是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。

比如对于 int32 类型的数字,一般需要 4 个 byte 来表示。但是采用 Varint,对于很小的 int32 类型的数字,则可以用 1 个 byte 来表示。当然凡事都有好的也有不好的一面,采用 Varint 表示法,大的数字则需要 5 个 byte 来表示。从统计的角度来说,一般不会所有的消息中的数字都是大数,因此大多数情况下,采用 Varint 后,可以用更少的字节数来表示数字信息。Varint 中的每个 byte 的最高位 bit 有特殊的含义,如果该位为 1,表示后续的 byte 也是该数字的一部分,如果该位为 0,则结束。

 

简单来说,可变长的整数,每个字节的最高位标识后续的byte是否还是该数字的一部分

因为所用到的数字(表示长度或者表示其他)往往比较小,用可变长整数信息的表示非常紧凑,自然需要更少的资源。比如(protobuf应用)网络上传输的字节数更少,需要的 IO 更少;(protobuf 或 leveldb)存盘的所用空间更少

继续阅读

发表在 leveldb | 标签为 , , , , , | 留下评论

Leveldb源码 skiplist 跳表

概述

跳表是一种有序数据结构,它通过在每个节点维持多个指向其他节点的指针,从而达到快速访问节点的目的。

跳表基于并联的链表,其效率可比拟于二叉查找树(对于大多数操作需要O(log n)平均时间)。

插入新节点时,该节点层数随机。

更详尽的解释参见维基百科或自行google。贴两张维基百科的图片,便于大家理解。

大家有兴趣也可以去看redis的skiplist实现(zskiplist)。

继续阅读

发表在 leveldb | 标签为 , | 留下评论

Leveldb源码 Arena 内存池

概述

一个高性能的服务器端程序,内存的使用非常重要。多次的申请和释放引起的内存碎片,一旦碎片到达一定程度,即使剩余内存总量够用,但由于缺乏足够的连续空闲空间,导致内存不够用的假象。为了避免小块内存的频繁分配,最简单的做法就是申请大块的内存,多次分给客户。

一般的策略都是:
小块内存–>从内存池里拿;不够的话,内存池向系统申请新的大块。
大块内存–>直接问系统拿。

如果对这块感兴趣可以自行研究一下:ptmalloc、tcmalloc、Jemalloc、Nginx内存池。

Redis默认使用jemalloc,我们公司的项目也是使用jemalloc。

Redis的Makefile片段:

# Default allocator
ifeq ($(uname_S),Linux)
	MALLOC=jemalloc
else
	MALLOC=libc
endif

# Backwards compatibility for selecting an allocator
ifeq ($(USE_TCMALLOC),yes)
	MALLOC=tcmalloc
endif

ifeq ($(USE_TCMALLOC_MINIMAL),yes)
	MALLOC=tcmalloc_minimal
endif

ifeq ($(USE_JEMALLOC),yes)
	MALLOC=jemalloc
endif

ifeq ($(USE_JEMALLOC),no)
	MALLOC=libc
endif

好,言归正传。leveldb中实现了一个”一次性”的简单的内存池Arena,不是所有的地方都使用了这个内存池,主要是memtable(skiplist)使用

继续阅读

发表在 leveldb | 标签为 , , | 留下评论