Eric 发布的文章

Linux 上的 Java 线程优先级

最近看了一篇关于 Java 在 Linux 上设置线程优先级的文章, 受益良多. 原来写了这么多年 Java 代码, 线程优先级在 Linux 上竟然是默认竟然是无效的. 这个网站可能在国内无法访问:
Thread Priority in Linux and Java

大致意思是:
Java 有个 Flag 是: -XX:ThreadPriorityPolicy. 它的值默认是 0, 还有另外一个值是 1. 对着 2 个值的解释是:

0 : Normal.
    VM chooses priorities that are appropriate for normal
    applications. On Solaris NORM_PRIORITY and above are mapped
    to normal native priority. Java priorities below
    NORM_PRIORITY map to lower native priority values. On
    Windows applications are allowed to use higher native
    priorities. However, with ThreadPriorityPolicy=0, VM will
    not use the highest possible native priority,
    THREAD_PRIORITY_TIME_CRITICAL, as it may interfere with
    system threads. On Linux thread priorities are ignored
    because the OS does not support static priority in
    SCHED_OTHER scheduling class which is the only choice for
    non-root, non-realtime applications.
1 : Aggressive.
    Java thread priorities map over to the entire range of
    native thread priorities. Higher Java thread priorities map
    to higher native thread priorities. This policy should be
    used with care, as sometimes it can cause performance
    degradation in the application and/or the entire system. On
    Linux this policy requires root privilege.

所以, 要想 Java 的优先级在 Linux 上生效, 要这么做(既要 sudo 又要设置 flag):

sudo java -XX:ThreadPriorityPolicy=1 ThreadPrioTest

就能看到下面的优先级输出:

supra@suprabox:~$ ps -l -m  -p $(pgrep java)
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
4 -     0 30388 30387 99   -   - - 2033328 -    pts/2    00:03:00 java
1 S     0     -     - 62  84   4 -     - -      -        00:00:14 -
1 S     0     -     - 66  83   3 -     - -      -        00:00:15 -
1 S     0     -     - 68  82   2 -     - -      -        00:00:15 -
1 S     0     -     - 74  81   1 -     - -      -        00:00:17 -
1 S     0     -     - 78  80   0 -     - -      -        00:00:17 -
1 S     0     -     - 76  79  -1 -     - -      -        00:00:17 -
1 S     0     -     - 79  78  -2 -     - -      -        00:00:18 -
1 S     0     -     - 85  77  -3 -     - -      -        00:00:19 -
1 S     0     -     - 88  76  -4 -     - -      -        00:00:20 -
1 S     0     -     - 90  75  -5 -     - -      -        00:00:20 -

上面的测试文件内容:

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.Random;

public class ThreadPrioTest {

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = new Runnable() {
            public void run() {
                StringBuffer sb = new StringBuffer("");
                while (true) {
                    Random r = new Random();
                    sb.append(r.nextDouble());
                    sb.setLength(300);
                    sb.trimToSize();
                }
            }
        };
        
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(runnable, "ThreadTest_ " + i);
            t.setPriority(i+1);
            t.start();
        }
        
        ThreadMXBean threadMxBean = ManagementFactory.getThreadMXBean();
        ThreadInfo[] threadInfos = threadMxBean.dumpAllThreads(false, false);
        String name = null;
        for (int i = 0; i < 1000; i++) {
            System.out.println();
            for (ThreadInfo info : threadInfos) {
                name = info.getThreadName();
                if (name.contains("ThreadTest")) {
                    System.out.println(name + " " + threadMxBean.getThreadCpuTime(info.getThreadId()));
                }
            }
            
            Thread.sleep(30000);
        }
        
    }    
}

运行的结果(可以看到占用时间的差别):

supra@suprabox$ sudo java -XX:ThreadPriorityPolicy=1 ThreadPrioTest

ThreadTest_ 0 31115073
ThreadTest_ 1 88458982
ThreadTest_ 2 57653703
ThreadTest_ 3 47742366
ThreadTest_ 4 87852759
ThreadTest_ 5 81910285
ThreadTest_ 6 87854949
ThreadTest_ 7 74596281
ThreadTest_ 8 73344747
ThreadTest_ 9 79761175

   ...
ThreadTest_ 0 94775603625
ThreadTest_ 1 100380714690
ThreadTest_ 2 100932887535
ThreadTest_ 3 108438013681
ThreadTest_ 4 113013836327
ThreadTest_ 5 118102801890
ThreadTest_ 6 121683982580
ThreadTest_ 7 127791024493
ThreadTest_ 8 130891651909
ThreadTest_ 9 134574994529

如果你是在一个有大于 10 个 CPU 核的机器上跑这个测试, 可能结果并不是这样, 因为他们又足够的 CPU, 所以各个优先级的线程使用 CPU 差别不是很大. 所以线程优先级只有在 CPU 不够用的机器上才比较明显.

另外 java 里面关于线程优先级的 flags:

supra@suprabox:~$ java -XX:+PrintFlagsFinal -version | grep riori
     intx CompilerThreadPriority                   = -1                                        {product} {default}
     intx JavaPriority10_To_OSPriority             = -1                                        {product} {default}
     intx JavaPriority1_To_OSPriority              = -1                                        {product} {default}
     intx JavaPriority2_To_OSPriority              = -1                                        {product} {default}
     intx JavaPriority3_To_OSPriority              = -1                                        {product} {default}
     intx JavaPriority4_To_OSPriority              = -1                                        {product} {default}
     intx JavaPriority5_To_OSPriority              = -1                                        {product} {default}
     intx JavaPriority6_To_OSPriority              = -1                                        {product} {default}
     intx JavaPriority7_To_OSPriority              = -1                                        {product} {default}
     intx JavaPriority8_To_OSPriority              = -1                                        {product} {default}
     intx JavaPriority9_To_OSPriority              = -1                                        {product} {default}
     intx ThreadPriorityPolicy                     = 0                                         {product} {default}
     bool ThreadPriorityVerbose                    = false                                     {product} {default}
     bool UseThreadPriorities                      = true                                   {pd product} {default}
     intx VMThreadPriority                         = -1                                        {product} {default}
openjdk version "11.0.11" 2021-04-20
OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.18.04)
OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.18.04, mixed mode, sharing)

关于之前 Java 里面关于那个 flag 的 bug:
http://www.akshaal.info/2008/04/javas-thread-priorities-in-linux.html
关于 Java 线程优先级的另外一个问答:
https://stackoverflow.com/questions/46936170/cassandra-and-java-9-threadprioritypolicy-42-is-outside-the-allowed-range

gdb

Debugging Symbol Table 把编译的二进制代码对应到响应的变量名, 方法名 和 行号. 符号表可以嵌入到二进制程序中或者在一个单独的文件中.

通过 -g 的参数把符号表嵌入到二进制代码中:

gcc -g hello.cc -o hello 

# 把 debug 信息分离出来
objcopy --only-keep-debug hello hello.debug
# 或者
cp hello hello.debug
strip --only-keep-debug hello.debug

# 去掉 debug 信息
strip --strip-debug --strip-unneeded hello

# 加入 debug 信息
objcopy --add-gnu-debuglink hello.debug hello

# debug 分离的信息
gdb -s hello.debug -e hello

# 或者 debug 启动之后
gdb
(gdb) exec-file  hello
(gdb) symbol-file hello.debug

基本的命令:

file a.out #指定要 debug 的二进制文件
run #启动程序
run 202 "abc" > out.txt # 给要执行的文件参数 并把结果放入 out.txt

break #添加断点到当前行
break myFuction #添加断点到 myFuction
break 5 #添加断点到第五行
break +5 #添加断点到 -> 从当前行往后数5行

info break #查看断点 
info threads

watch x == 3 #如果条件满足, 则暂停

delete 5 #删除第五个断点
clear #清除当前函数的断点

continue #继续运行直到下个断点或错误
finish #继续运行直到当前函数结束
step #运行下一行指令, 如果下一行是函数, 直接进入这个函数, 然后暂停
step 5 #运行后边的5条指令
next #运行下一行, 如果下一行是函数, 直接执行完这个函数

print var #打印变量 var 的值

set x = 3 #设置变量 x 的值为3
set x = y #设置变量 x 的值为 y 的值

bt #查看当前栈
up #到上一层帧
down #到下一层帧
stack #查看当前帧
return #从当前函数返回
select-frame 3 #

help list #打印 list 命令帮助
list 210 #打印出当前文件的第210行左右的行代码
list hello.c:43 #打印 hello.c 的第43行左右的代码
list hello.c:test #打印 hello.c 的test 函数

call myFunc() #执行 myFunc 函数
call myFunc(4) #执行 myFunc 函数
call strlen(str) #执行系统函数 strlen

display x #每一步都显示 x 的值
undisplay x #不要再每一步都显示 x 的值

一篇比较好的关于 GDB debug Java native 代码或 JDK 代码的文章:
https://medium.com/@pirogov.alexey/gdb-debug-native-part-of-java-application-c-c-libraries-and-jdk-6593af3b4f3f

Windows 上解密 chrome 的 https 流量

https 其实是使用传输层的 SSL/TLS 对内容进行加密. SSL/TLS 首先使用非对称加密传输客户端产生的 pre-master secret key. 然后之后使使用对称加密传输内存, 对称加密的的算法使用之前双方都有的 pre-master secret key 作为秘钥. 如果知道了 pre-master secret key, 那么就能解密 https 的流量了. 下面是 windows 上使用 chrome 截取 pre-master secret key 并使用 wireshark 解密的例子.

  1. 设置环境变量 SSLKEYLOGFILE 的值是本地一个文件
    win_env.png
  2. 重新启动 chrome;
  3. 随便访问一个 https 网站, 查看上面设置的文件里是不是已经有 key 产生;
  4. 启动 wireshark, 设置 TLS 的 key 文件.
    菜单: Edit -> Preferences -> Protocols -> TLS -> (pre-)master secret key log file
    wins_key.png
  5. 使用 wireshark 抓包, 或者 wincap 抓包. 抓包后最好使用 wireshark filter 一下. 找到 http1.1 或者 http2 的内容, 就能看到原文了
    wins_wire.png

Java 如何简单的诊断 https (SSL/TLS) 请求

如果你能控制整个 Java 应用的启动, 那么你可以很简单的加SSL/TLS 的 debug 参数去打印等多的信息. 比如:

java -Djavax.net.debug=all myApp
java -Djavax.net.debug=ss:plaintext myApp 

想查看更多可以用的参数: 必须找一个 App, 不能使用常见的 java -Djavax.net.debug=help -version

java -Djavax.net.debug=help myApp

一个访问百度首页的 (-Djavax.net.debug=ss:plaintext) 的输出:

javax.net.ssl|DEBUG|01|main|2021-08-06 07:43:20.943 PDT|SSLCipher.java:1769|Plaintext before ENCRYPTION (
  0000: 47 45 54 20 2F 20 48 54   54 50 2F 31 2E 30 0A 0A  GET / HTTP/1.0..
)
javax.net.ssl|DEBUG|01|main|2021-08-06 07:43:21.233 PDT|SSLCipher.java:1671|Plaintext after DECRYPTION (
  0000: 48 54 54 50 2F 31 2E 30   20 35 30 30 20 49 6E 74  HTTP/1.0 500 Int
  0010: 65 72 6E 61 6C 20 53 65   72 76 65 72 20 45 72 72  ernal Server Err
  0020: 6F 72 0D 0A 43 6F 6E 74   65 6E 74 2D 4C 65 6E 67  or..Content-Leng
  0030: 74 68 3A 20 30 0D 0A 43   6F 6E 74 65 6E 74 2D 54  th: 0..Content-T
  0040: 79 70 65 3A 20 74 65 78   74 2F 70 6C 61 69 6E 3B  ype: text/plain;
  0050: 20 63 68 61 72 73 65 74   3D 75 74 66 2D 38 0D 0A   charset=utf-8..
  0060: 44 61 74 65 3A 20 46 72   69 2C 20 30 36 20 41 75  Date: Fri, 06 Au
  0070: 67 20 32 30 32 31 20 31   34 3A 34 33 3A 32 31 20  g 2021 14:43:21 
  0080: 47 4D 54 0D 0A 53 65 72   76 65 72 3A 20 62 66 65  GMT..Server: bfe
  0090: 0D 0A 0D 0A                                        ....
)

更多信息参考:

  1. https://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/ReadDebug.html
  2. https://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.html#Debug
  3. https://access.redhat.com/solutions/973783

MAC 上解密 chrome 的 https 流量

Chrome 或者 Firefox 都支持: 如果设置了环境变量 SSLKEYLOGFILE, 就把 SSL/TLS 的 pre-master secret key 写到设置的文件里面去. 之后可以使用这个 pre-master secret key 文件在 wireshark 里面解密加密的流量.

----- 原来的步骤 1, 2 在2023年底不行了, 换成如下 --------------
在命令行执行:

SSLKEYLOGFILE=/tmp/google/ssl-key.log /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --user-data-dir=/tmp/google

---------- 补丁结束 ----------------

  1. 设置 SSLKEYLOGFILE 环境变量
    可以简单在命令行使用 export 命令 (记得之后打开 Chrome 要在这个命令行)

    export SSLKEYLOGFILE=~/ssh_key.log
  2. 在同一个命令行窗口打开 chrome

    open /Applications/Google\ Chrome.app/
  3. 在 chrome 随便访问一个 https 的网站, 检查 ~/ssh_key.log 是不是有内容
  4. 打开 wireshark 拦截流量或者 使用 tcpdump 有针对性的拦截

    sudo tcpdump host 103.144.218.5 -w mydump.pcap 
  5. 打开 wireshark, 分析这个加密的流量.
    显示设置SSL/TLS 的 pre-master secret key log 文件:
    菜单: preferences -> Protocols -> TLS (Wireshark 3.0 之前是 SSL):

    Preferences.png

  6. 关于 key log 文件的格式:https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format

    之后:

    1. 首先找到带有 data 的行
    2. 可以看 http 或 http 2 的数据
    3. 点击下面(3)的 Decrypted data, 其实里面看上去是乱码, 因为直接解密后还是压缩的, 所以是乱码;
    4. 点解下面(4)的 uncompressed entity body 可以看到加压后的明文
      xiaohui_pcap.png