快捷键 作用
Alt+T 搜索文本
Ctrl+1 Quick View导航
空格键 图形视图/汇编视图切换
Shift+F3 切换到Functions窗口
Shift+F4 切换到Names窗口
Shift+F7 切换到Segments窗口
Shift+F12 切换到Strings窗口
IDA View内
Ctrl+Enter 前进(函数调用)
Esc 后退(函数调用)

Linux内核编程一直是我很想掌握的一个技能。如果问我为什么,我也说不上来。
也许是希望有一天自己的名字也出现在内核开发组的邮件列表里?或是内核发行文件的CREDITS上?
也许是吧。其实更多的,可能是对于底层的崇拜,以及对于内核的求索精神。
想到操作系统的繁杂,想到软件系统之间的衔接,内心觉得精妙的同时,更是深深的迷恋。
所以从这篇文章开始,我要真正的走进Linux内核里了,让代码指引我,去奇妙的世界一探究竟。

在这篇文章中,一起来对内核说Hello World。

本次的编程环境:

  • CentOS 6.8
  • Linux centos 2.6.32-573.8.1.el6.x86_64

没有安装内核的,可能需要安装一下内核源码包

  • kernel-devel-2.6.32-642.4.2.el6.x86_64

    yum install kernel-devel-2.6.32-642.4.2.el6.x86_64

安装好之后,这个版本内核可以在/usr/src/linux找到。

然后话不多说,首先看代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//20160904
//kernel_hello_world.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

static int __init lkp_init(void){
printk("Hello,World! --from the kernel space...\n");
return 0;
}

static void __exit lkp_cleanup(void){
printk("Goodbye,World! --leaving kernel space...");
}

module_init(lkp_init);
module_exit(lkp_cleanup);

以上代码是kernel_hello_world.c内容。
作为内核模块,在编译的时候,Makefile文件这样写:

1
2
#File:Makefile
obj-m += kernel_hello_world.o

然后可以通过这条命令来编译:

1
make -C /usr/src/linux SUBDIRS=$PWD modules

编译好以后,目录下面的文件可能是这样子:

kernel_hello_world.ko.unsigned  kernel_hello_world.o  Module.symvers
kernel_hello_world.c   kernel_hello_world.mod.c        Makefile
kernel_hello_world.ko  kernel_hello_world.mod.o        modules.order

有这么多文件被生成,其中kernel_hello_world.ko就是本次编译出来的内核模块文件,在Linux内核中有很多这样的模块,它们可能充当着不同的角色,可能是驱动,也可能是各种设备。

这个模块会在/var/log/message文件中打印一行字,即Hello,World! –from the kernel space…

可以使用insmod kernel_hello_world.ko来将这个模块载入到内核,使用lsmod来查看是否已经加载,使用rmmod kernel_hello_world.ko来卸载这个模块。

可以tail /var/log/message来看一下是否成功执行了呢?

Hello,Kernel.

本次的编程环境:

  • CentOS 6.8
  • Linux centos 2.6.32-573.8.1.el6.x86_64

在内核的源代码中定义了很多进程和进程调度相关的内容。其实Linux内核中所有关于进程的表示全都放在进程描述符这个庞大的结构体当中,关于这个结构体的内容和定义,可以在内核的linux/sched.h文件中找到。
现在就来通过编程实现对进程描述符的操作,主要是读取。至于修改等操作,将在后面的内容中提到。
通过对进程描述符的读取,可以获取进程的一切内容,包括进程的ID,进程的地址空间等等。

不多说,上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//20160912
//currentptr.c

#include <linux/tty.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/module.h>

/*Function To Write Msg To TTY*/
void tty_write_message(struct tty_struct *tty,char *msg){
if(tty && tty->ops->write){
tty->ops->write(tty,msg,strlen(msg));
}
return;
}

static int my_init(void){
char *msg="Hello tty!\n";
tty_write_message(current->signal->tty,msg);
printk("Hello -- from the kernel...\n");
printk("Parent pid: %d(%s)\n",current->parent->pid,current->parent->comm);
printk("Current pid: %d(%s)\n",current->pid,current->comm);
printk("Current fs: %d\n",current->fs);
printk("Current mm: %d\n",current->mm);
return 0;
}

static void my_cleanup(void){
printk("Goodbye -- from the kernel...\n");
}

module_init(my_init);
module_exit(my_cleanup);

以上的代码,主要就是通过引入内核头文件,进而引用进程描述符中的指针,并通过这种方式获取当前进程和相关进程的描述信息。
Makefile文件如下:

#Makefile
obj-m += currentptr.o

编译的指令:

make -C /usr/src/linux SUBDIRS=$PWD modules

然后通过insmod把模块装载进内核,首先tty输出了Hello tty!
同时在/var/log/message中,模块打印出了这些内容:

Sep 12 10:35:36 centos kernel: Hello -- from the kernel...
Sep 12 10:35:36 centos kernel: Parent  pid: 2235(bash)
Sep 12 10:35:36 centos kernel: Current pid: 13197(insmod)
Sep 12 10:35:36 centos kernel: Current fs: 927961856
Sep 12 10:35:36 centos kernel: Current mm: 932613056

分别是这个进程的相关信息。
对于进程描述符的定义,在本次实验用来编译的内核源码包(kernel-devel-2.6.32-642.4.2.el6.x86_64)中,
进程描述符具体定义在include/linux/sched.h的1326行往后。

需要参考的进程具体信息都在其中,可随时参考,以备不时之需。

定义类

定义类的过程就是定义类的属性的过程。类的属性就是类的静态属性的简称,指类内包含的各项数据。类的服务被称为成员函数或方法。

继承(extends)

通过定义继承方法,子类可以获得父类的所有属性和方法。

接口(implements)

说明当前类中实现了哪个接口定义的功能和方法,是Java语言中实现多重继承的一种机制

修饰符

  • 访问控制符
  • 抽象类说明符
  • 最终说明符

类的属性

描述了类内部的信息,又称为类的静态属性。类属性为简单变量。

【修饰符】 变量类型 变量名【=变量初值】
【修饰符】 类名 对象名 【=new 类名(实际参数列表)】

若使用另一个类的对象作为当前类所定义的类的属性,要保证该对象所在的类在当前类中是可已被当前类所引用的

类属性的修饰符

  • 访问控制符
  • 静态修饰符static
  • 最终修饰符final
  • 易失修饰符volatile
  • 过渡修饰符transient

类的方法(成员函数)

用来规定类属性上的操作,实现类内部功能的机制,也是类与外界交互的窗口。

声明方法的语法:
【修饰符】返回值类型 方法名(参数列表)
Trows 例外名1,例外名2,… {
方法体:
局部变量声明;
语句序列;
}

方法的修饰符

  • 访问控制符
  • 静态修饰符static
  • 抽象修饰符abstract
  • 最终修饰符final
  • 同步修饰符synchronous
  • 本地修饰符native

类的构造函数

特殊之处:

  • 构造函数的方法名与类名相同
  • 构造函数没有返回类型
  • 构造函数的主要作用是完成对类对象的初始化工作

Java在声明类时,可以不定义构造函数,系统会自动为该类生成一个默认的构造函数,此时这个构造函数的名字与类名相同,没有任何形式参数,也不完成任何操作。

在创建一个类的新对象的同时,系统会自动调用该类的构造函数为新对象初始化。

类的访问控制符只有一个public

属性和方法的访问控制符有:

  • public
  • private
  • protected
  • private protected

对于同一个包中的类可以不需任何说明,方便的互相访问和引用。

在不同包中的类,只有他们都声明为public时,然后再在程序头部声明import后才可以被访问和引用相应的类。

publici修饰的类的属性称为公共属性,如果公共属性属于一个公共类,则可以被所有的其他类使用。

public修饰符会造成安全性和数据封装性下降,应尽量减少public属性的使用。

缺省访问控制符规定该类只能被同一个包中的类访问和引用,而不可以被其他包中的类引用,这种特性称为包访问性。

类内的属性和方法如果没有访问控制符来限定,也说明他们具有包访问性,可以被同一个包中的其他类所访问和调用。

用private修饰的属性或方法只能被该类自身所访问和修改,而不能被任何其他类,包括该类的子类来获取和引用。

procted修饰的属性可以被该类自身和包中的类访问。

静态属性

被static修饰的属性称为静态属性,静态属性是一个公共的存储单元。任何一个类的对象访问它时取到的都是相同的值,任何一个类的对象去修改它时,都是在对同一个内存单元做操作。

static修饰符修饰的属性是属于类的公共属性

static修饰符修饰的方法是属于整个类的方法

不用static修饰符修饰的方法是属于某个具体对象或实例的方法

声明一个方法的static至少有三重含义:

  • 调用这个方法时,应该使用类名做前缀,而不是某一个具体的对象名
  • 非static的方法是属于某个对象的方法,在这个对象创建时对象的方法在内存中拥有自己的专用代码段,而static的方法是属于整个类的,它在内存中的代码段将随着类的定义而分配和装载,不被任何一个对象专有。
  • static方法只能处理static类型的数据

静态初始化器

是由关键字static引导的一对大括号括起的语句组。

静态初始化器与构造函数的区别:
构造函数是对每个新创建的对象初始化,而静态初始化器是对每个类进行初始化。

构造函数是在用new运算符产生新对象时由系统自动执行,而静态初始化器则是在它所属的类加载入内存时由系统调用运行的。

不同于构造函数,静态初始化器不是方法,没有方法名,返回值和参数列表。

抽象类

当一个类被生命为abstract时,这个类被称为抽象类。

所谓抽象类就是没有具体实例对象的类。

抽象类是它所有子类的公共属性的集合。

使用抽象类的一大优点就是可以利用这些公共属性来提高开发和维护程序的效率。

最终类,最终属性,最终方法,终结器

  • 最终类

  • 如果一个类被final修饰符所修饰和限定,说明这个类不可能拥有子类

  • 最终属性

final就是用来修饰常量的修饰符,一个类的成员变量如果被声明为final,那么它的取值在程序的执行过程中都不会改变,也就是一个常量

用final修饰符说明常量时:

需要说明常量的数据类型

需要同时指出常量的具体取值

因为所有类的对象的常量成员,其数值都固定一致,为了节省空间,常量通常都被声明为static。

final修饰符所修饰的类方法,是功能和内部语句都不能被更改的最终方法,即是不能被当前类的子类重载的方法。

final方法固定所对应的具体操作,防止子类对父类关键方法的错误的重定义,保证了程序的安全性和正确性。

所有已被private修饰符限定为私有的方法,以及所包含在final类中的方法,都被缺省的认为是final的。

  • 终结器
  • 终结器是回收对象时执行的方法。

protected void finalize(){}

终结器是一个名为finalize的方法,没有产生列表和返回值。

volatile修饰符

被volatile修饰的类的属性可能同时被几个线程控制和修改,通常用来修饰受外部输入的属性。

以Debian系发行版Kali为例

首先,安装fcitx输入法框架和fcitx-pinyin作为默认的拼音输入法:

1
apt-get install fcitx fcitx-pinyin

安装好之后,系统应该会多出这两个图标(在gnome桌面下)

之后,将系统的输入源切换到fcitx,运行

1
im-config

或是在菜单中,点击这个红圈圈住的图标:

一路下一步,在最后一步选择fcitx

切换好之后,在fcitx中配置pinyin输入法:

选择pinyin即可,不需要选择google拼音(感觉并没有fcitx-pinyin好用)
然后重启系统,或是重启X11系统。

然后就可以用啦,默认是Ctrl+空格切换输入法:

如果不想用Ctrl+空格切换输入法,可以改为使用shift切换输入法,同样是在fcitx中配置:

之前研究Linux设备驱动时做的零零散散的笔记,整理出来,方便以后复习。

驱动程序的的角色

  • 提供机制

例如:
unix图形界面分为X服务器和窗口会话管理器,X服务器理解硬件及提供统一的接口给用户程序,窗口管理器实现了特别的策略但对硬件一无所知。

目标:实现对策略透明

  • 划分内核

进程管理
负责创建和销毁进程,并处理它们与外部的联系(输入和输出)。
实现了多个进程在一个单个或几个CPU之上的抽象。

内存管理
为每一个进程在有限的可用资源上建立了虚拟地址空间。

文件系统
在非结构化的硬件之上建立了一个结构化的文件系统。

设备控制
全部设备的控制操作都由特定的寻址设备相关的代码来进行。

网络
系统负责在程序和网络接口之间递送数据报文。

可加载模块

Linux特性:可以在运行时扩展由内核提供的特性,可以在系统正在运行的时候增加内核的功能(也可以去除)。
每块可以在运行时添加到内核的代码被称为一个模块。通过insmod和rmmod程序去连接。

设备和模块的分类

字符设备

字符(char)设备是一种可以当作字节流来存取的设备。这样的驱动常常实现open,close,read,write系统调用。
例如:文本控制台(/dev/console),串口(/dev/ttyS0)

块设备

通过位于/dev目录的文件系统节点来存取,可以驻有文件系统。与字符设备的区别在于内部管理数据的方式上–块设备允许一次传送任意数据的字节。

网络接口

负责发送和接收数据报文

安全问题

小心对待输入,未初始化的内存等,从内核获取的任何内存应当清零或者在其可用之前进行初始化。

版本编号

Linux系统中使用的每一个软件包存有自己的发行版本号,它们之间存在相互依赖性。

版权条款

字符驱动

scull
Simple Character Utility for Loading Localities

设备编号

[root@centos ~]$ ll /dev
total 0                                        主编号,次编号
drwxr-xr-x. 2 root    root         640 May 12 22:24 block
crw-------. 1 root    root     10, 234 May 12 22:24 btrfs-control
drwxr-xr-x. 3 root    root          60 May 12 22:24 bus
lrwxrwxrwx. 1 root    root           3 May 12 22:24 cdrom -> sr0
drwxr-xr-x. 3 root    root          80 May 12 22:24 cpu
crw-rw----. 1 root    root     10,  62 May 12 22:24 crash
drwxr-xr-x. 6 root    root         120 May 12 22:24 disk
brw-rw----. 1 root    disk    253,   0 May 12 22:24 dm-0
drwxr-xr-x. 2 root    root          60 May 12 22:24 dri
lrwxrwxrwx. 1 root    root           3 May 12 22:24 fb -> fb0
crw-rw----. 1 root    root     29,   0 May 12 22:24 fb0
crw-rw-rw-. 1 root    root      1,   7 May 12 22:24 full

主编号标识设备相连的驱动,次编号被内核用来决定引用哪个设备。

环境:
Windows 10
Python 2.7.10

0x01 安装PyQt4
在这个页面下载,注意选对版本。
https://riverbankcomputing.com/software/pyqt/download
我选择的版本是 PyQt4-4.11.4-gpl-Py2.7-Qt4.8.7-x64.exe

0x02 编写测试脚本

1
2
3
4
5
6
7
8
9
import sys
from PyQt4 import QtGui

app = QtGui.QApplication(sys.argv)
widget = QtGui.QWidget()
widget.resize(250, 150)
widget.setWindowTitle('PyQt')
widget.show()
sys.exit(app.exec_())

如果成功运行并弹出一个空白的窗口,说明PyQt4已经安装上了。

0x03 使用PyQt4的QtWebKit实现解析Dom

待续。

VC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

main(){
int a = 1;
int b = 2;
int c;
__asm{
mov eax,a
mov ebx,b
mov ecx,1h
add eax,ebx
mov c,ecx
}
printf("%x\n", c);
}

GCC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

main(){
int a = 1;
int b = 2;
int c;
asm(
"add %2,%0" //1
:"=g"(c) //2
:"0"(a),"g"(b) //3
:"memory" //4
);
printf("%x\n", c);
}

1.数组遍历方法总结

1
array = (1..10).to_a 
1
2
3
4
5
length = array.length 
length.times do t
print "#{array[t]} "
end
puts "n"
1
2
3
4
5
length = array.length-1 
for i in 0..length do
print "#{array[i]} "
end
puts "n"
1
2
3
4
for i in array do 
print "#{i} "
end
puts "n"
1
2
array.each{x print x," "} 
puts "n"
1
2
3
4
5
6
7
length = array.length 
i = 0
while i< length do
print "#{array[i]} "
i = i+1
end
puts "n"
1
2
3
4
5
6
7
length = array.length 
i = 0
until i==length do
print "#{array[i]} "
i += 1
end
puts "n"
1
2
array.each_index do i 
print "#{array[i]} "

2.Ruby连接数据库

  • mysql
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    require 'mysql'  
    begin
    db = Mysql.init
    db.options(Mysql::SET_CHARSET_NAME, 'utf8')
    db = Mysql.real_connect("127.0.0.1", "root", "123456", "test", 3306)
    db.query("SET NAMES utf8")
    db.query("drop table if exists tb_test")
    db.query("create table tb_test (id int,
    text LONGTEXT) ENGINE=MyISAM DEFAULT CHARSET=utf8")
    db.query("insert into tb_test (id, text) values (
    1,'first line'),(2,'second line')")
    printf "%d rows were inserted\n",db.affected_rows
    rslt = db.query("select text from tb_test")
    while row = rslt.fetch_row do
    puts row[0]
    end
    rescue Mysql::Error => e
    puts "Error code: #{e.errno}"
    puts "Error message: #{e.error}"
    puts "Error SQLSTATE: #{e.sqlstate}" if e.respond_to?("sqlstate")
    ensure
    db.close if db
    end
  • redis
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #!/bin/ruby

    require 'redis'

    def writeToFile(file,content)
    fp = File.new(file,"a+")
    if fp
    fp.syswrite(content)
    else
    puts "..."
    end
    end

    def connect(host)
    redis = Redis.new(:host => host,:port => 6379)
    redis.info.keys.each do |key|
    puts "#{key}:\t"+redis.info["#{key}"]
    end
    end

    connect("1.1.1.1")
  • sqlite3
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    require 'sqlite3'

    db = SQLite3::Database.new('test.db')

    db.execute("create table test(
    ID integet not null,
    Username varchar(20) null,
    Password varchar(64) null)")
    db.execute("insert into test(ID.Username,Password)
    values('0','admin','admin')")
    db.execute("select * from test")
    db.execute("update test set password='12345' where id=0")

3.Ruby socket

  • 服务端:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    require 'socket'

    server = TCPServer.open('0.0.0.0', 8080)
    loop do
    Thread.start(server.accept) do |client|
    begin
    while true
    puts "#{client.to_i} online"
    data = client.read()
    throw "empty" if data.empty?
    #puts data.length
    puts data
    end
    rescue Exception => e
    puts "#{client.to_i} offline"
    end
    end
    end
  • 客户端:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    require 'socket'

    hostname = '127.0.0.1'
    port = 8080
    buf = 'test'
    s = TCPSocket.open(hostname, port)
    s.write buf
    sleep(1)
    s.close

4.ruby gem 文档服务

rubygems.org上的gem文档访问起来太慢了,其实gem本身就自带doc的功能
安装gem的时候会默认安装相应gem的doc,如果不想占用空间安装doc,则gem install XXX –no-doc 即可。
使用下列命令可以启动gem自带的文档:

1
gem server --port 1234

然后访问 http://localhost:1234 就可以查看相关的gem文档。

5.ruby改变控制台输出内容的颜色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
puts "\033[1m前景色\033[0m\n"  
puts " \033[30mBlack (30)\033[0m\n"
puts " \033[31mRed (31)\033[0m\n"
puts " \033[32mGreen (32)\033[0m\n"
puts " \033[33mYellow (33)\033[0m\n"
puts " \033[34mBlue (34)\033[0m\n"
puts " \033[35mMagenta (35)\033[0m\n"
puts " \033[36mCyan (36)\033[0m\n"
puts " \033[37mWhite (37)\033[0m\n"
puts ''
puts "\033[1m背景色\033[0m\n"
puts " \033[40m\033[37mBlack (40), White Text\033[0m\n"
puts " \033[41mRed (41)\033[0m\n"
puts " \033[42mGreen (42)\033[0m\n"
puts " \033[43mYellow (43)\033[0m\n"
puts " \033[44mBlue (44)\033[0m\n"
puts " \033[45mMagenta (45)\033[0m\n"
puts " \033[46mCyan (46)\033[0m\n"
puts " \033[47mWhite (47)\033[0m\n"
puts ''
puts "\033[1m其他\033[0m\n"
puts " Reset (0)"
puts " \033[1mBold (1)\033[0m\n"
puts " \033[4mUnderlined (4)\033[0m\n"

colors

6.一些比较特别的包

Ruby json gem
https://rubygems.global.ssl.fastly.net/gems/json-1.8.3.gem
树莓派wiringpi gpio包
http://pi.gadgetoid.com/article/wiringpi-as-a-ruby-gem

7.安装rvm的正确姿势

参考自:http://rvm.io/rvm/install
首先添加gpg公钥:

1
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3

安装稳定版本的rvm

1
curl -sSL https://get.rvm.io | bash -s stable --ruby

8.解决kali2.0中RVM不能编译ruby-2.3.3

  • 错误详情:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    ruby-2.3.3 - #compiling.......................................................................-
    Error running '__rvm_make -j4',
    showing last 15 lines of /usr/local/rvm/log/1488041042_ruby-2.3.3/make.log
    exts.mk:210: recipe for target 'ext/openssl/all' failed
    make[1]: *** [ext/openssl/all] Error 2
    make[1]: *** Waiting for unfinished jobs....
    installing default nkf libraries
    compiling objspace_dump.c
    linking shared-object json/ext/generator.so
    make[2]: Leaving directory '/usr/local/rvm/src/ruby-2.3.3/ext/json/generator'
    linking shared-object objspace.so
    make[2]: Leaving directory '/usr/local/rvm/src/ruby-2.3.3/ext/objspace'
    linking shared-object nkf.so
    make[2]: Leaving directory '/usr/local/rvm/src/ruby-2.3.3/ext/nkf'
    make[1]: Leaving directory '/usr/local/rvm/src/ruby-2.3.3'
    uncommon.mk:203: recipe for target 'build-ext' failed
    make: *** [build-ext] Error 2
    ++ return 2
    There has been an error while running make. Halting the installation.

查看/usr/local/rvm/log/1488041042_ruby-2.3.3/make.log发现是openssl版本过老导致的。

  • 解决:
    第一步:先安装用于rvm的openssl:
    1
    rvm pkg install openssl

第二步:编译安装ruby,指定openssl目录(我的是/usr/local/rvm/usr/)

1
rvm install ruby-2.3.3 --with-openssl-dir=/usr/local/rvm/usr/

9.设置Gems默认源为ruby-china

现在没有淘宝源了,只有ruby-china源

1
gem sources --add https://gems.ruby-china.org/ --remove https://rubygems.org/

设置Bundler默认源为ruby-china:

1
bundle config mirror.https://rubygems.org https://gems.ruby-china.org

这样修改以后,即使Gemfile中指定了Source,也会用国内的源。

Python
先按F5,之后将下面的命令保存,再设置快捷键。

1
cmd /k  c:\python27\python "$(FULL_CURRENT_PATH)" & PAUSE & EXIT

我使用的是Ctrl+F9
之后按Ctrl+s保存更改,再按Ctrl+F7即可快速在cmd中运行代码,方便调试。

Ruby

1
cmd /k  C:\Ruby22-x64\bin\ruby "$(FULL_CURRENT_PATH)" & PAUSE & EXIT
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×