破译SQLServer for Linux预览版的3.5GB内存限制 (RHEL篇)

破解SQLServer for Linux预览版的3.5GB内存限制 (RHEL篇)

微软发布了SQLServer for Linux,但是安装竟然需要3.5GB内存,这让大部分云主机用户都没办法尝试这个新东西
这篇我将讲解如何破解这个内存限制
要看关键的可以直接跳到第6步,只需要替换4个字节就可以破解这个限制

  1. 首先按照微软的给出的步骤安装和配置
    https://docs.microsoft.com/zh-cn/sql/linux/sql-server-linux-setup-red-hat

  2. 到执行/opt/mssql/bin/sqlservr-setup时可以看到这个错误

    sqlservr: This program requires a machine with at least 3250 megabytes of memory.
  3. 按错误文本查找消息在哪个文件里面

    [root@localhost ~]# cd /opt/mssql/bin/
    [root@localhost bin]# grep -irn "3250"
    [root@localhost bin]# grep -irn "megabytes of memory"
    Binary file sqlpackage matches
    Binary file sqlpackage matches
    Binary file sqlservr matches
    [root@localhost bin]# strings sqlservr | grep "megabytes of memory"
    %s: This program requires a machine with at least %zu megabytes of memory.
    [root@localhost bin]# strings sqlpackage | grep "megabytes of memory"
    %s: This program requires a machine with at least %zu megabytes of memory.

    看来sqlservr和sqlpackage会检测这个限制,并且这个限制是一个常量

  4. 查找错误消息的位置

    [root@localhost bin]# hexdump -C sqlservr | less

    找到这里

    0006baf0  72 69 6e 67 29 00 25 73  3a 20 54 68 69 73 20 70  |ring).%s: This p|
    0006bb00  72 6f 67 72 61 6d 20 72  65 71 75 69 72 65 73 20  |rogram requires |

    可以看到消息在0006baf6的位置

  5. 查找调用错误消息的位置

    [root@localhost bin]# objdump -C -S sqlservr | less

    找到这里

       23940:       48 8d 35 af 81 04 00    lea    0x481af(%rip),%rsi        # 6baf6
       23947:       31 c0                   xor    %eax,%eax
       23949:       48 89 ca                mov    %rcx,%rdx
       2394c:       48 89 d9                mov    %rbx,%rcx
       2394f:       e8 6c e4 fe ff          callq  11dc0 <fprintf@plt>
       23954:       bf 01 00 00 00          mov    $0x1,%edi
       23959:       e8 e2 e1 fe ff          callq  11b40 <exit@plt>

    判断的函数在这里

       238e0:       55                      push   %rbp
       238e1:       48 89 e5                mov    %rsp,%rbp
       238e4:       53                      push   %rbx
       238e5:       48 83 ec 78             sub    $0x78,%rsp
       // 把这个函数接收的第二个参数放到rbx
       // 参考 https://en.wikipedia.org/wiki/X86_calling_conventions (System V AMD64 ABI)
       238e9:       48 89 f3                mov    %rsi,%rbx
       // 调用sysinfo获取内存大小
       // rdi是第一个参数,是一个在堆栈中的struct sysinfo
       // 参考 https://linux.die.net/man/2/sysinfo
       238ec:       48 8d 7d 88             lea    -0x78(%rbp),%rdi
       238f0:       e8 3b e3 fe ff          callq  11c30 <sysinfo@plt>
       // 偏移量的计算如下
       // -0x78: uptime (struct sysinfo的开头地址)
       // -0x70: loads[3]
       // -0x58: totalram
       // -0x50: freeram
       // -0x48: sharedram
       // -0x40: bufferram
       // -0x38: totalswap
       // -0x30: freeswap
       // -0x28: procs (short为什么占8个字节?看https://en.wikipedia.org/wiki/Data_structure_alignment)
       // -0x20: totalhigh
       // -0x18: freehigh
       // -0x10: mem_unit (同样,int 4个字节 align 4个字节)
       // 计算出rax = totalram * mem_unit
       238f5:       8b 45 f0                mov    -0x10(%rbp),%eax
       238f8:       48 0f af 45 a8          imul   -0x58(%rbp),%rax
       // 如果rax小于rbx则跳到23909,即显示内存不足并退出
       238fd:       48 39 d8                cmp    %rbx,%rax
       23900:       72 07                   jb     23909
       23902:       48 83 c4 78             add    $0x78,%rsp
       23906:       5b                      pop    %rbx
       23907:       5d                      pop    %rbp
       23908:       c3                      retq

    调用判断的函数的代码在这里

       // 这里的第二个参数是3250000000,可以看到内存的限制值是一个常量
       // 0xc1b71080 = 3250000000
       1486a:       be 80 10 b7 c1          mov    $0xc1b71080,%esi
       1486f:       4c 89 e7                mov    %r12,%rdi
       14872:       e8 69 f0 00 00          callq  238e0

    顺道再用hexdump查找一下有多少处地方用了80 10 b7 c1,结果是只有一处

    00014860  00 00 48 89 df e8 66 15  00 00 be 80 10 b7 c1 4c  |..H...f........L|
    00014870  89 e7 e8 69 f0 00 00 0f  57 c0 0f 29 85 70 ff ff  |...i....W..).p..|
  6. 使用python修改代码
    改条件判断的jb或者改8010b7c1都可以,我这里把8010b7c1改成更小的值0080841e(512M)

    [root@localhost bin]# mv sqlservr sqlservr.old
    [root@localhost bin]# python
    >>> a = open("sqlservr.old", "rb").read()
    >>> b = a.replace("\x80\x10\xb7\xc1", "\x00\x80\x84\x1e")
    >>> open("sqlservr", "wb").write(b)
    [root@localhost bin]# chmod +x sqlservr

    可以继续替换掉sqlpackage中的限制值,但是不替换也可以使用

  7. 继续配置sqlserver

    [root@localhost bin]# /opt/mssql/bin/sqlservr-setup
    [root@localhost bin]# systemctl status mssql-server

    如果你执行完命令后没有看到服务正常启动,可能是之前的配置没有成功导致的
    删除mssql的数据文件夹并重试即可

    [root@localhost bin]# rm -rf /var/opt/mssql
    [root@localhost bin]# /opt/mssql/bin/sqlservr-setup

    正常启动后可以看到

● mssql-server.service - Microsoft(R) SQL Server(R) Database Engine
   Loaded: loaded (/usr/lib/systemd/system/mssql-server.service; enabled; vendor preset: disabled)
   Active: active (running) since Mon 2016-12-05 22:50:06 EST; 20s ago
 Main PID: 2625 (sqlservr)
   CGroup: /system.slice/mssql-server.service
           ├─2625 /opt/mssql/bin/sqlservr
           └─2638 /opt/mssql/bin/sqlservr

Dec 05 22:50:10 localhost.localdomain sqlservr[2625]: 2016-12-06 03:50:10.85 spid17s     Server is listening on [ 0.0.0.0 ...433].
Dec 05 22:50:10 localhost.localdomain sqlservr[2625]: 2016-12-06 03:50:10.87 Server      Server is listening on [ 127.0.0....434].
Dec 05 22:50:10 localhost.localdomain sqlservr[2625]: 2016-12-06 03:50:10.89 Server      Dedicated admin connection suppor...1434.
Dec 05 22:50:10 localhost.localdomain sqlservr[2625]: 2016-12-06 03:50:10.89 spid17s     SQL Server is now ready for clien...ired.
Dec 05 22:50:11 localhost.localdomain sqlservr[2625]: 2016-12-06 03:50:11.77 spid6s      Starting up database 'tempdb'.
Dec 05 22:50:12 localhost.localdomain sqlservr[2625]: 2016-12-06 03:50:12.02 spid6s      The tempdb database has 1 data file(s).
Dec 05 22:50:12 localhost.localdomain sqlservr[2625]: 2016-12-06 03:50:12.02 spid20s     The Service Broker endpoint is in...tate.
Dec 05 22:50:12 localhost.localdomain sqlservr[2625]: 2016-12-06 03:50:12.03 spid20s     The Database Mirroring endpoint i...tate.
Dec 05 22:50:12 localhost.localdomain sqlservr[2625]: 2016-12-06 03:50:12.09 spid20s     Service Broker manager has started.
Dec 05 22:50:12 localhost.localdomain sqlservr[2625]: 2016-12-06 03:50:12.14 spid5s      Recovery is complete. This is an ...ired.
Hint: Some lines were ellipsized, use -l to show in full.

启动成功后使用微软提供的命令行工具连接也可以,使用windows上的客户端连接也可以
https://docs.microsoft.com/zh-cn/sql/linux/sql-server-linux-setup-tools
下图是2G内存上运行的mssql
破译SQLServer for Linux预览版的3.5GB内存限制 (RHEL篇)
破译SQLServer for Linux预览版的3.5GB内存限制 (RHEL篇)

Ubuntu上的破解会不一样,因为Ubuntu安装前会运行检测程序,如何破解将在下一篇讲解

题外话

  • mssql for linux有日期限制和联网验证,预计正式版以后免费的可能性很小
  • mssql在linux上编译开启了pie选项并且没有符号表导出,这让gdb跟踪变得很困难,但这次破解只需要静态分析
  • mssql的本体封在了/opt/mssql/lib/sqlservr.sfp里面,如果需要破解其他限制可能还需要花功夫研究这个文件
7楼DJLNET
农夫大神就是叼 O(∩_∩)O哈哈~
6楼源河
强!
5楼JRoger
农夫要亮
4楼mspeer
膜拜
3楼桦仔
After a few more SFP files are opened for certificates and NetFX4, and then we end up at sqlservr.sfp. And inside here, it loads things familiar to deep dive SQL Server pros…first we see the program binary load sqlservr.exe, SqlDK.dll, sqllang.dll, SQLOS.dll, and sqlmin.dll. I omitted some output for readability.,,open(“/opt/mssql/lib/sqlservr.sfp”, O_RDONLY) = 7,…omitted,pread(7, “sqlservr.exe\0”, 13, 13398) = 13,…omitted,pread(7, “SqlDK.dll\0”, 10, 14079) = 10,…omitted,pread(7, “sqllang.dll\0″, 12, 14382) = 12,…omitted,pread(7, “SQLOS.dll\0”, 10, 14418) = 10,…omitted,pread(7, “sqlmin.dll\0”, 11, 14511) = 11
2楼jinzhenshui
牛X
1楼troy.cui
微软那么吃内存,有点瞎搞